带有 AOT 的延迟加载模块 - TypeError: '' 从 NGINX 提供时不是一个函数

Posted

技术标签:

【中文标题】带有 AOT 的延迟加载模块 - TypeError: \'\' 从 NGINX 提供时不是一个函数【英文标题】:Lazy Loaded Modules with AOT - TypeError: '' is not a function when served from NGINX带有 AOT 的延迟加载模块 - TypeError: '' 从 NGINX 提供时不是一个函数 【发布时间】:2018-12-29 07:09:03 【问题描述】:

这是我自己复制的 dist 文件:

dist with Lazy Modules, AOT dist with Lazy Modules, no AOT dist with AOT, no Lazy Modules

细分:

当使用 npm 包 webpack-dev-server 或 live-server 服务时,我的 dist 构建,带有 AOT 和延迟加载模块可以正常工作 只有当我将 dist 复制到 nginx html 目录并且 NGINX 提供文件时,我才会在 Firefox 和 Chrome 中看到 javascript 错误 我尝试了许多不同的 webpack 配置。 我没有在任何 Typescript 文件中导入我的延迟加载模块 关闭 AOT 编译后,我的应用程序和惰性模块可以从 NGINX 正常运行 TypeError: '' is not a function 错误来自 NGINX 提供的延迟加载模块

我正在使用官方 Angular 包 @ngtools/webpack 将 AOT 编译添加到我的 Angular 5 应用程序中。 This article 解释了如何使用 @ngtools/webpack 将 AOT 添加到 Webpack 构建项目中。很简单,虽然文章没有提到add the Lazy Load module file paths 到tsconfig-aot.json 所需的步骤。如果没有这一步,AOT 就会失败。

所有工作都很棒的本地主机:

npm run serve

我的npm run serve 命令是在内存中编译的,资源是使用npm 包webpack-dev-server 从内存提供给本地主机的。

当我部署到我的开发服务器时,编译文件存储在磁盘上,我的开发服务器使用 NGINX 提供资源。

我有延迟加载的模块,加载时会在 Firefox 中抛出类似这样的奇怪错误:

TypeError: i0.\u0275crt is not a function

这在 Chrome 中:

ERROR TypeError: i0.ɵcrt is not a function

详细了解 Chrome 中的错误,这里是引发错误的源映射代码行:

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

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

在我的开发服务器上,node_modules 与我的 dist 文件夹处于父级:

我的开发服务器上的文件:

以下是从 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;

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(文件数组包括延迟加载的模块路径)


    "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
    

这是我的 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 "$corsoptions";
        

        if ($request_method = 'GET') 
            set $cors "$corsget";
        
        if ($request_method = 'POST') 
            set $cors "$corspost";
        

        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;
    

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

ma​​in.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;
  

【问题讨论】:

这可能是个愚蠢的问题,但是在将目标文件复制到服务器之前,您是否使用了 build-* 脚本之一? 在将输出从 dist 复制到我的开发服务器之前,我目前正在使用构建开发脚本 也许先用 localhost 转到浏览器上的网络选项卡(调试窗口),然后与您的服务器进行比较,可以帮助您确定是否缺少任何东西。 @closevoter 亲爱的 closevoter,作为一个过去曾因神秘的 angular/webpack 错误而苦苦挣扎的人,这对我来说似乎是一个非常有效的问题...... 这是个好建议,谢谢,试试看 【参考方案1】:

这是因为当你使用 AOT 时,代码被丑化了一些有趣的 UTF-8 符号; ɵ i0.ɵcrt 在你的情况下

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

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

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

【讨论】:

非常感谢,我花了很多时间试图找出问题所在 不用担心。当我下载你的文件时很容易重现:)【参考方案2】:

将 /etc/nginx 文件夹中的当前 nginx.conf 文件替换为以下内容,

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

延迟加载的路由将包含哈希值,location ~* "^/[a-z0-9]40.(css|js)$" 这将解决问题

【讨论】:

【参考方案3】:

在 Angular 8 中,我们需要将旧的延迟加载方式更改为动态导入,并且需要在 tsconfig.json 文件中更改 module: 'esnext' 和 target: 'es2015'。

【讨论】:

以上是关于带有 AOT 的延迟加载模块 - TypeError: '' 从 NGINX 提供时不是一个函数的主要内容,如果未能解决你的问题,请参考以下文章

延迟加载Angular / Ionic 3 Component AOT“不是已知元素:错误”

Angular 9 嵌套延迟加载模块,带有嵌套路由器出口

如何处理 angular2 延迟加载路由失败

Angular 6/7 AOT:动态模板渲染 - 为模块加载 JitCompiler

为延迟加载或子模块使用命名路由器出口

Angular 6 - 指定延迟加载块的路径