Lazy Loaded Modules with AOT - TypeError: '' is not a function when served from NGINX

Bri*_*den 6 aot typescript webpack angular

Here are my dist files to reproduce yourself:

The breakdown:

  • My dist build, with AOT and Lazy Loaded modules works fine when served with npm packages webpack-dev-server or live-server
  • It is only when I copy dist to NGINX html directory and NGINX serves the files that I see Javascript errors in Firefox and Chrome
  • I have tried many different webpack configurations.
  • I am not importing my Lazy Loaded modules in any Typescript file
  • With AOT compilation OFF my app and Lazy Modules serve fine from NGINX
  • The TypeError: '' is not a function error is coming from Lazy Loaded Modules being served with NGINX

I am using the official Angular package @ngtools/webpack to add AOT compilation to my Angular 5 app. This article explains how to use @ngtools/webpack to add AOT to a Webpack build project. Very simple, though the article does not mention the needed step to add the Lazy Load module file paths to tsconfig-aot.json. AOT fails with out that step.

All works great localhost with:

npm run serve
Run Code Online (Sandbox Code Playgroud)

My npm run serve command is an in memory compilation and the resources are served localhost from memory using the npm package webpack-dev-server.

When I deploy to my development server, the compilation files are stored on disk and my dev server serves the resources with NGINX.

I have lazy loaded modules that, when loaded are throwing strange errors like this in Firefox:

TypeError: i0.\u0275crt is not a function
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

And this in Chrome:

ERROR TypeError: i0.?crt is not a function
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

通过Chrome中的错误了解更多细节,以下是抛出错误的源代码行代码: 在此输入图像描述

我看到var的创建i0是这一行:

var i0 = __webpack_require__(/*! @angular/core */ "./node_modules/@angular/core/esm5/core.js");
Run Code Online (Sandbox Code Playgroud)

在我的开发服务器上,node_modules在父级别与我的dist文件夹: 在此输入图像描述

我的dev服务器上的文件:

在此输入图像描述

以下是从localhost提供的资源文件的比较:

在此输入图像描述

这是从开发服务器提供的资源文件: 在此输入图像描述

好的,所以这里有我的配置和npm包版本:

webpack.config

 var merge = require('webpack-merge'),
    htmlPlugin = require('html-webpack-plugin'),
    revPlugin = require('webpack-rev-replace-plugin'),
    config = require('./build.config.json'),
    path = require('path'),
    extendedDefinePlugin = require('extended-define-webpack-plugin'),
    webpackDelPlugin = require('webpack-del-plugin'),
    openBrowserPlugin = require('open-browser-webpack-plugin'),
    uglifyJSPlugin = require('uglifyjs-webpack-plugin');
const AotPlugin = require('@ngtools/webpack').AngularCompilerPlugin;
//import {AngularCompilerPlugin} from '@ngtools/webpack';

//Note : in package.json the last variable (dev) is the param delivered to this function { env: 'dev' }. 
module.exports = function (env) {
    console.log('env configuration', env.env);
    /**
     * configPerTarget is merged with build.config.json based on the env passed
     * currently no configuration properties, this configPerTarget not in use per se, keeping just in case - Ogden 4-12-2018
     */
    var configPerTarget = {
        localhost: {
        },
        development: {
        },
        test: {
        },
        staging: {
        },
        production: {
        },
        maintenance: {
        }
    };

    // Note : '__dirname' is the root file path.
    const ROOT_DIR = path.resolve(__dirname);
    const DIST_DIR = path.join(ROOT_DIR, config.dist);

    // If no env make it dev
    if (!env) {
        env = {};
        env.env = config.envDevelopment;
    }

    //merge config with env specific configPerTarget
    config = merge(config, configPerTarget[env.env]);

    // this takes path variables from build.config.json and builds it with given env
    var appConfigPath = config.envs + config.appConfig.replace('{env}', env.env);


    var webPackConfig = {
        entry: ['babel-polyfill', config.src + config.entry],//main.ts
        output: {
            path: path.resolve(__dirname, config.dist),
            filename: config.buildjs,
            sourceMapFilename: config.buildjsmap,
            chunkFilename: '[id].[hash:6].chunk.js'
        },
        module: {
            rules: [
                { test: /\.html$/, use: 'raw-loader' },
                { test: /\.css$/, use: 'raw-loader' },
                {
                    test: /\.ts$/,
                    loaders: [
                        'ts-loader',
                        'angular2-template-loader',
                        'angular-router-loader']
                },
                {
                    test: /\.scss$/,
                    exclude: /node_modules/,
                    loaders: ['style-loader', 'css-loader', 'sass-loader'],
                },
                //For images. 
                { test: /\.(jpe?g|png|gif|svg)$/i, loader: 'file-loader?name=app/assets/images/[name].[ext]' },
                {
                    test: /\.(ttf|eot|woff|woff2)$/,
                    loader: 'file-loader'
                },
            ]
        },
        //https://webpack.js.org/configuration/devtool/
        //Webpack 4.4 has its own mode development and production, which are environment modes
        //do Webpack 4.4 is handling the devtool sourcemap config where in the past it was not
        //looks like we no longer have to worry about setting devtool
        //https://github.com/damianobarbati/yarsk/blob/50b6f352a13ec2e778fa8b252f915550b6132964/config/webpack.config.js#L110
        //devtool: config.devtool,
        resolve: {
            modules: [__dirname + path.sep + 'src', __dirname, 'node_modules'],
            extensions: ['.js', '.ts', '.scss', '.css']
        },
        plugins: [
            new htmlPlugin({
                template: config.src + config.index
            }),
            new revPlugin({
                cwd: config.src,
                files: '**/*.html',
                outputPageName: function (filename) {
                    return filename;
                },
                modifyReved: function (filename) {
                    return filename.replace(/(\/style\/|\/script\/)/, '')
                }
            }),
            //Makes AppConfig variable available in the application code. 
            new extendedDefinePlugin({
                AppConfig: require(appConfigPath)
            }),
            //Usefull if you need remove some files or folders before compilation processes. 
            //currently not used (no dist file).
            new webpackDelPlugin({ match: path.join(DIST_DIR, '*.*') }),
            //opens browser after compilation.
            new openBrowserPlugin({ url: 'http://localhost:8080' })
        ]
    }

    //********************************AOT Compilation*************************************** */
    //-- AOT Compilation from this point on, currently AOT runs in all environments
    //this seems helpful because you get to see AOT build errors before pushing to build server
    //the downside might be more mangled code and harder to debug source code...

    if (env.env === config.envLocalhost) return webPackConfig;

    webPackConfig.module.rules.push(
        { test: /\.ts$/, loaders: ['@ngtools/webpack'] }
    );

    webPackConfig.plugins.push(new AotPlugin({
        tsConfigPath: './tsconfig-aot.json',
        //mainPath: path.resolve('./src/main.ts'),
        entryModule: path.join(config.src, 'app/app.module#AppModule')
    }));

    webPackConfig.optimization = {
        minimizer: [
            new uglifyJSPlugin({
                uglifyOptions: {
                    output: {
                        comments: false,
                        ascii_only: true
                    }
                }
            })
        ]
    }

    return webPackConfig;
}
Run Code Online (Sandbox Code Playgroud)

的package.json

{
  "name": "tsl-frontend",
  "version": "0.1.0",
  "scripts": {
    "test": "karma start",
    "build-localhost": "webpack --mode development --progress --colors --env.env localhost",
    "build-development": "webpack --mode development --progress --colors --env.env development",
    "build-staging": "webpack --mode production --progress --colors --env.env staging",
    "build-production": "webpack --mode production -p --progress --colors --env.env production",
    "build-maintenance": "webpack --mode production -p --progress --colors --env.env maintenance",
    "serve": "webpack-dev-server --mode development --inline --progress --colors --env.env development",
    "serve-production": "webpack-dev-server --mode production --inline --progress --colors --env.env development",
    "serve-localhost": "webpack-dev-server --mode development --inline --progress --colors --env.env localhost",
    "serve-host": "webpack-dev-server --host 0.0.0.0 --port 80 --disable-host-check --mode development --inline --progress --colors --env.env localhost",
    "serve-maintenance": "webpack-dev-server --mode development --inline --progress --colors --env.env maintenance"
  },
  "dependencies": {
    "@angular/animations": "^5.2.11",
    "@angular/cdk": "^2.0.0-beta.12",
    "@angular/common": "^5.2.11",
    "@angular/compiler": "^5.2.11",
    "@angular/compiler-cli": "^5.2.11",
    "@angular/core": "^5.2.11",
    "@angular/forms": "^5.2.11",
    "@angular/http": "^5.2.11",
    "@angular/material": "^2.0.0-beta.12",
    "@angular/platform-browser": "^5.2.11",
    "@angular/platform-browser-dynamic": "^5.2.11",
    "@angular/platform-server": "^5.2.11",
    "@angular/router": "^5.2.11",
    "@ng-bootstrap/ng-bootstrap": "^1.1.2",
    "@types/file-saver": "^1.3.0",
    "angular2-jwt": "^0.2.3",
    "angular2-text-mask": "^8.0.5",
    "bootstrap": "^4.1.2",
    "chart.js": "^2.7.2",
    "file-saver": "^1.3.8",
    "font-awesome": "^4.7.0",
    "moment": "2.18.1",
    "moment-timezone": "0.5.13",
    "ng2-bootstrap-modal": "1.0.1",
    "ng2-charts": "^1.6.0",
    "ng2-drag-drop": "^2.9.2",
    "ng2-page-scroll": "^4.0.0-beta.12",
    "ng2-toastr": "^4.1.2",
    "popper.js": "^1.14.3",
    "reflect-metadata": "0.1.8",
    "rxjs": "5.5.5",
    "systemjs": "0.19.40",
    "typescript": "^2.9.2",
    "xlsx": "^0.11.19",
    "zone.js": "^0.8.26"
  },
  "devDependencies": {
    "@ngtools/webpack": "^6.0.8",
    "@servicestack/client": "^1.0.14",
    "@types/file-saver": "^1.3.0",
    "@types/jasmine": "^2.8.8",
    "@types/node": "7.0.7",
    "angular-router-loader": "^0.6.0",
    "angular2-router-loader": "^0.3.5",
    "angular2-template-loader": "^0.6.2",
    "babel-polyfill": "^6.26.0",
    "css-loader": "^0.28.11",
    "extended-define-webpack-plugin": "^0.1.3",
    "extract-text-webpack-plugin": "^3.0.2",
    "file-loader": "^1.1.11",
    "file-saver": "^1.3.8",
    "html-webpack-plugin": "^4.0.0-alpha",
    "jasmine": "^2.99.0",
    "karma": "^1.7.0",
    "karma-sourcemap-loader": "^0.3.7",
    "karma-webpack": "^2.0.13",
    "ng-intercom": "^1.0.0-beta.5-2",
    "ng2-tree": "^2.0.0-rc.11",
    "node-sass": "^4.9.2",
    "open-browser-webpack-plugin": "0.0.5",
    "path": "^0.12.7",
    "raw-loader": "^0.5.1",
    "sass-loader": "^6.0.7",
    "style-loader": "^0.13.2",
    "text-mask-addons": "^3.7.2",
    "toposort": "^1.0.7",
    "ts-loader": "^4.4.2",
    "webpack": "^4.16.1",
    "webpack-cli": "^2.1.5",
    "webpack-del-plugin": "0.0.1",
    "webpack-dev-server": "^3.1.4",
    "webpack-merge": "^4.1.3",
    "webpack-rev-replace-plugin": "^0.1.1"
  }
}
Run Code Online (Sandbox Code Playgroud)

tsconfig-aot.json(文件数组包含延迟加载的模块路径)

{
    "compilerOptions": {
        "target": "es5", //most browsers currently understand this version of Javascript
        "experimentalDecorators": true, //Angular2 uses Component,Injectable etc
        "emitDecoratorMetadata": true, //Required for Angular2 to use the metadata in our components
        //"sourceMap": true
        "types": [
            "node",
            "jasmine"
        ],
        // "typeRoots": [
        //     "node_modules/@types"
        // ],
        "lib": [
            "es2015",
            "es2015.iterable",
            "dom"
        ]
    },
    "exclude": [
        "node_modules"
    ],
    "files": [
        "src/app/app.module.ts",
        "src/main.ts",
        "src/app.d.ts",
        "src/app/sandbox/sandbox.module.ts",
        "src/app/supplier-xchange/supplier-xchange.module.ts",
        "src/app/company-profile/company-profile.module.ts",
        "src/app/bom/bom.module.ts",
        "src/app/custom-price-column/custom-price-column.module.ts",
        "src/app/neca/neca.module.ts"
    ],
    "angularCompilerOptions": {
        "genDir": "aot", // Specify where Angular can create temporary AOT files
        "skipMetadataEmit": true // Don't generate not necessary metadata files. They are useful only if you're publishing an Angular UI library
    }
}
Run Code Online (Sandbox Code Playgroud)

这是我的NGINX配置:

daemon off;
user  nginx;
worker_processes  2;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
    use epoll;
    accept_mutex off;
}


http {
    include       /etc/nginx/mime.types;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    client_max_body_size 300m;
    client_body_buffer_size 300k;
    large_client_header_buffers 8 64k;

    gzip  on;
    gzip_http_version 1.0;
    gzip_comp_level 6;
    gzip_min_length 0;
    gzip_buffers 16 8k;
    gzip_proxied any;
    gzip_types text/plain text/css text/xml text/javascript application/xml application/xml+rss application/javascript application/json;
    gzip_disable "MSIE [1-6]\.";
    gzip_vary on;

    include /etc/nginx/conf.d/*.conf;
}


server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;

    # API Server
    # location /api/ {
    #     proxy_pass ${MY_API_URL}/;
    # }

    # Main
    location / {
        set $cors "true";
       if ($http_origin ~* (http:\/\/d\.mywebsite\.com\S*)$) {
            set $cors "true";
        }

        if ($request_method = 'OPTIONS') {
            set $cors "${cors}options";
        }

        if ($request_method = 'GET') {
            set $cors "${cors}get";
        }
        if ($request_method = 'POST') {
            set $cors "${cors}post";
        }

        if ($cors = "trueget") {
            add_header 'Access-Control-Allow-Origin' "$http_origin";
            add_header 'Access-Control-Allow-Credentials' 'true';
        }

        if ($cors = "truepost") {
            add_header 'Access-Control-Allow-Origin' "$http_origin";
            add_header 'Access-Control-Allow-Credentials' 'true';
        }

        if ($cors = "trueoptions") {
            add_header 'Access-Control-Allow-Origin' "$http_origin";
            add_header 'Access-Control-Allow-Credentials' 'true';
            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
            add_header 'Access-Control-Allow-Headers' 'Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,Keep-Alive,X-Requested-With,If-Modified-Since';
            add_header 'Content-Length' 0;
            add_header 'Content-Type' 'text/plain charset=UTF-8';
            return 204;
        }

        root   /usr/share/nginx/html;
        index  index.html index.htm;
        try_files $uri$args $uri$args/ /index.html;
    }

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}
Run Code Online (Sandbox Code Playgroud)

认为这些文件对这个问题很重要,但以防万一:

main.ts

//CSS STYLES
import './styles';
import 'reflect-metadata';
//Zone JS is required by Angular itself.
import 'zone.js/dist/zone';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { enableProdMode } from '@angular/core';


//remaining in ProdMode even in dev because of  ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked errors
//http://www.allenhashkey.com/web-development/angular2/angular-2-expression-changed-after-it-has-been-checked-exception/
enableProdMode();
// if (!AppConfig.isDevelopment) {
//     enableProdMode();
// }


platformBrowserDynamic().bootstrapModule(AppModule)
    .then(success => console.log('Bootstrap success'))
    .catch(err => console.error("Bootstrap module failure: ", err));
Run Code Online (Sandbox Code Playgroud)

app.module.ts

// Vendor
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { LocationStrategy, HashLocationStrategy } from '@angular/common';
import { Injector } from '@angular/core';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { Ng2DragDropModule } from 'ng2-drag-drop';
import { ToastModule } from 'ng2-toastr/ng2-toastr';
import { Ng2PageScrollModule } from 'ng2-page-scroll';
// Routing
import { AppRoutingModule, routableComponents } from './app-routing.module';
//Components
import { AppComponent } from './app.component';
//Shared Module
import { SharedModule } from './@shared/shared.module';
//Feature Modules
import { CoreModule } from './@core/core.module';
import { DashboardModule } from './dashboard/dashboard.module';
import { ProductModule } from './product/product.module';
import { SearchModule } from './search/search.module';
import { LoginModule } from './login/login.module';
import { ExampleModule } from './example/example.module';
import { ProfileModule } from './profile/profile.module';
import { ResetPasswordModule } from './reset-password/reset-password.module';
import { EdataFlexModule } from './e-data-flex/e-data-flex.module';
import { SubmittalManagerModule } from './submittal-manager/submittal-manager.module';
import { PimModule } from './pim/pim.module';
import { AnalyticsModule } from './analytics/analytics.module';
import { InviteUserModule } from './invite-user/invite-user.module';
import { DownloadsModule } from './downloads/downloads.module';
import { SettingsModule } from './settings/settings.module';
import { ChangeBulletinModule } from './change-bulletin/change-bulletin.module';
//Singletons - A Singleton Service shall only be kept in app.module.ts "providers" (array)
//and it shall not be placed in any other component or service provider (array).
import { TokenService } from './@core/auth/token.service';
// Intercom Module
import { IntercomModule } from 'ng-intercom';
import { BootstrapModalModule } from 'ng2-bootstrap-modal';

@NgModule({
  imports: [
    SharedModule,
    BrowserModule,
    DashboardModule,
    ProductModule,
    SearchModule,
    ProfileModule,
    ExampleModule,
    LoginModule,
    CoreModule,
    ResetPasswordModule,
    EdataFlexModule,
    SubmittalManagerModule,
    PimModule,
    AnalyticsModule,
    InviteUserModule,
    DownloadsModule,
    SettingsModule,
    ChangeBulletinModule,
    //Do not import feature modules below "AppRoutingModule"
    AppRoutingModule,
    BrowserAnimationsModule,
    BootstrapModalModule.forRoot({container:document.body}),
    Ng2PageScrollModule.forRoot(),
    Ng2DragDropModule.forRoot(),
    ToastModule.forRoot(),
    IntercomModule.forRoot({
      appId:AppConfig.intercom["appId"], // TSO App Id
      updateOnRouterChange : true // will automatically run 'update' on router event changes.
    })
  ],
  declarations: [
    AppComponent,
    routableComponents
  ],
  providers: [
    { provide: LocationStrategy, useClass: HashLocationStrategy },
    TokenService
  ],
  bootstrap: [AppComponent]

})
export class AppModule {
  /**
     * Allows for retrieving singletons using `AppModule.injector.get(MyService)`
     * This is good to prevent injecting the service as constructor parameter.
     */
  static injector: Injector;
  constructor(injector: Injector) {
    AppModule.injector = injector;
  }
}
Run Code Online (Sandbox Code Playgroud)

Dav*_*vid 3

这是因为当你使用 AOT 时,代码会被一些有趣的 UTF-8 符号丑化;\xc9\xb5i0.\xc9\xb5crt你的情况下

\n\n

您需要告诉 nginx 使用 UTF-8 字符集

\n\n
server {\n  listen 80 default_server;\n  listen [::]:80 default_server;\n  server_name _;\n\n  charset UTF-8; #<==== Add this\n
Run Code Online (Sandbox Code Playgroud)\n