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






