angular - Lata moduler med AOT - TypeError: '' är inte en funktion när den serveras från NGINX


Här är mina dist-filer för att reproducera själv:

  • dist med Lazy Modules, AOT
  • dist med lata moduler, ingen AOT
  • dist med AOT, inga Lazy-moduler

Uppdelningen:

  • Min dist-build, med AOT och Lazy Loaded-moduler fungerar bra när den serveras med npm-paketen webpack-dev-server eller live-server
  • Det är först när jag kopierar dist till NGINX html-katalog och NGINX servar filerna som jag ser Javascript-fel i Firefox och Chrome
  • Jag har provat många olika webpack-konfigurationer.
  • Jag importerar inte mina lazy-loadade moduler i någon Typescript-fil
  • Med AOT-kompilering AVSTÄNGD fungerar min app och Lazy Modules bra från NGINX
  • TypeError: '' är inte en funktion-felet kommer från Lazy Loaded-moduler som levereras med NGINX

Jag använder det officiella Angular-paketet @ngtools/webpack för att lägga till AOT-kompilering till min Angular 5-app. Den här artikeln förklarar hur man använder @ngtools/webpack för att lägga till AOT i ett Webpack-byggprojekt. Väldigt enkelt, även om artikeln inte nämner det nödvändiga steget att lägga till filvägarna för Lazy Load-modulen i tsconfig-aot.json. AOT misslyckas utan det steget.

Allt fungerar bra på localhost med:

npm run serve

Mitt npm run serve-kommando är en kompilering i minnet och resurserna serveras på localhost från minnet med npm-paketet webpack-dev-server.

När jag distribuerar till min utvecklingsserver lagras kompilationsfilerna på disken och min utvecklingsserver levererar resurserna med NGINX.

Jag har moduler som laddas lat, och när de laddas kastar de konstiga fel som detta i Firefox:

TypeError: i0.\u0275crt is not a function

Och detta i Chrome:

ERROR TypeError: i0.ɵcrt is not a function

För att gå mer i detalj med felet i Chrome, här är källkods-kartans kodrad som orsakar felet:

Jag ser att skapandet av variabeln i0 är denna rad:

var i0 = __webpack_require__(/*! @angular/core */ "./node_modules/@angular/core/esm5/core.js");

På min utvecklingsserver finns node_modules på överordnad nivå med min dist-mapp:

Mina filer på min utvecklingsserver:

Här är en jämförelse av resursfilerna som serveras från localhost:

Och här är resursfilerna som serveras från utvecklingsservern:

Okej, så här kommer min konfiguration och versioner av npm-paket:

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;
}

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"
  }
}

tsconfig-aot.json (filsarrayen inkluderar sökvägar till lat laddade moduler)

{
    "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
    }
}

Och här är min NGINX-konfiguration:

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;
    }
}

Tror att dessa filer har betydelse för detta problem, men bara för säkerhets skull:

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

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;
  }
}

Svar #1

Det beror på att när du använder AOT blir koden ful med några roliga UTF-8-symboler; ɵ i i0.ɵcrt i ditt fall

Du behöver tala om för nginx att använda UTF-8 teckenuppsättning

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

  charset UTF-8; #<==== Add this

Svar #2

Byt ut din nuvarande nginx.conf-fil i /etc/nginx-mappen med följande,

#worker_processes 2;

events {
    worker_connections 1024;
}
http {
   sendfile on;

   gzip on;
   gzip_disable "msie6";

   gzip_vary on;
   gzip_proxied any;
   gzip_comp_level 6;
   gzip_buffers 16 8k;
   gzip_http_version 1.1;
   gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

    log_format timed_combined '$remote_addr - $remote_user [$time_local] '
    '"$request" $status $body_bytes_sent '
    '"$http_referer" "$http_user_agent" '
    '$request_time $upstream_response_time $pipe';

    access_log /var/log/nginx/access.log timed_combined;
    error_log /var/log/nginx/error.log;

    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    server {
        listen 80;
        server_name _;
        return 404;
    }

   server {
        listen 80;
        server_name www.example.com;

        location / {
           root /home/pokusr/propok-frontend/dist;
           try_files $uri /index.html;
        }
        location ~* "^/[a-z0-9]{40}.(css|js)$" {
            root /home/usr/proj_path/dist/;
            access_log off;
            expires max;
        }
    }
}

Lat laddade rutter kommer att ha hashvärden i sig och location ~* "^/[a-z0-9]{40}.(css|js)$" { detta kommer att lösa problemet

Relaterade artiklar