Angular Universal does not work with Angular Google Maps
E

1

7

Tech: Angular Cli, Angular version 7, Angular Google Maps, Firebase Functions.

Issue: I'm trying to serve my angular universal app but getting an error for angular google maps when building.

Error I get:

/Users/test/Public/Leisure/leisure-app/functions/node_modules/@agm/core/services/managers/info-window-manager.js:1
(function (exports, require, module, __filename, __dirname) { import {
Observable } from 'rxjs';
                                                              ^^^^^^

SyntaxError: Unexpected token import
    at createScript (vm.js:80:10)
    at Object.runInThisContext (vm.js:139:10)

Seems that Angular Universal doesnt like third party libraries.

The resource I followed: https://hackernoon.com/deploying-angular-universal-v6-with-firebase-c86381ddd445

My App Module:

import { AgmCoreModule } from '@agm/core';
import { AngularFireAuthModule } from '@angular/fire/auth';
import { AngularFireDatabase } from '@angular/fire/database';
import { AngularFireModule } from '@angular/fire';
import { ServiceWorkerModule } from '@angular/service-worker';
import { AngularFireStorageModule } from '@angular/fire/storage';
import { AngularFirestoreModule } from '@angular/fire/firestore';
import { BrowserModule } from '@angular/platform-browser';
import { CommonModule } from '@angular/common';
import { GooglePlaceModule } from 'ngx-google-places-autocomplete';
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
import { NgModule, NO_ERRORS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { NgxErrorsModule } from '@hackages/ngxerrors';
import { ReactiveFormsModule, FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { TruncateModule } from 'ng2-truncate';
import { MetaModule } from '@ngx-meta/core';
import { PreventDoubleSubmitModule } from 'ngx-prevent-double-submission';

// Core App
import { AppComponent } from './app.component';
import { CoreInterceptor } from './interceptor';
import { environment } from '../environments/environment';

// Modules
import { SharedModule } from './shared/shared.module';
import { CoreModule } from './services/core.module';
import { LayoutModule } from './layouts/layout.module';

// Sections
import { COMPONENTS } from './components/index';
import { ROUTES } from './app.routes';

// Guards
import { AuthGuard } from './guards/auth.guard';
import { CreditGuard } from './guards/credit.guard';

@NgModule({
  declarations: [
    AppComponent,
    COMPONENTS
  ],
  imports: [
    CommonModule,
    FormsModule,
    AngularFireModule.initializeApp(environment.firebase),
    AngularFireAuthModule,
    AngularFirestoreModule,
    AngularFireStorageModule,

    // This is the Angular google maps module causing issue on build
    AgmCoreModule.forRoot({
      apiKey: environment.googleApiKey,
      libraries: ['places']
    }),

    GooglePlaceModule,
    LayoutModule,
    BrowserModule.withServerTransition({ appId: 'test123' }),
    PreventDoubleSubmitModule.forRoot(),
    TruncateModule,
    MetaModule.forRoot(),
    HttpClientModule,
    NgxErrorsModule,
    ReactiveFormsModule,
    RouterModule.forRoot(ROUTES),
    CoreModule,
    SharedModule,
    ServiceWorkerModule.register('/ngsw-worker.js', { enabled: environment.production })
  ],
  providers: [
    { provide: HTTP_INTERCEPTORS, useClass: CoreInterceptor, multi: true },
    AuthGuard,
    CreditGuard,
    AngularFireDatabase
  ],
  exports: [ RouterModule ],
  schemas: [ NO_ERRORS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }

Command I run: serve: npm run build && firebase serve --only functions

Latest Error (for npm run serve:ssr):

enter image description here

Error code from serve:ssr:

e.Lb=function(a,b,c){a=this.ka.J[String(a)];if(!a)return!0;a=a.concat();for(var d=!0,f=0;f<a.length;++f){var g=a[f];if(g&&!g.Sa&&g.capture==b){var k=g.listener,p=g.Ob||g.src;g.Eb&&this.Le(g);d=!1!==k.call(p,c)&&d;}}return d&&0!=c.Be};e.jb=function(a,b,c,d){return this.ka.jb(String(a),b,c,d)};var ub=h.JSON.stringify;function vb(a,b){this.Sf=100;this.ef=a;this.ug=b;this.Zb=0;this.Pb=null;}vb.prototype.get=function(){if(0<this.Zb){this.Zb--;var a=this.Pb;this.Pb=a.next;a.next=null;}else a=this.ef();return a};vb.prototype.put=function(a){this.ug(a);this.Zb<this.Sf&&(this.Zb++, a.next=this.Pb, this.Pb=a);};function I(){this.lc=this.Va=null;}var xb=new vb(function(){return new wb},function(a){a.reset();});I.prototype.add=function(a,b){var c=this.Af();c.set(a,b);this.lc?this.lc.next=c:this.Va=c;this.lc=c;};I.prototype.remove=function(){var a=null;this.Va&&(a=this.Va, this.Va=this.Va.next, this.Va||(this.lc=null), a.next=null);return a};I.prototype.wg=function(a){xb.put(a);};I.prototype.Af=function(){return xb.get()};function wb(){this.next=this.scope=this.Gc=null;}
wb.prototype.set=function(a,b){this.Gc=a;this.scope=b;this.next=null;};wb.prototype.reset=function(){this.next=this.scope=this.Gc=null;};function yb(a){h.setTimeout(function(){throw a;},0);}var zb;
Elka answered 4/4, 2019 at 13:55 Comment(4)
I've read in some places that you can white list the third party libraries using a webpack config file. But not sure how to do that.Elka
The comments in this issue may give you a few hints. One comment suggests using import-export or disabling AoT compiling as workarounds. Another comment suggests adding --bundle-dependencies=all to the ng build command or "bundleDependencies": "all" to the configurations in angular.json.Utopia
@ConnorsFan those links dont seem to work for an angular version 7 appElka
@Elka before you say it's not working in v7 have a look here, don't know if you tried with ng: #39997155Rosenberg
W
6

TL;DR:

Source code and DEMO

enter image description here


The issue here is that @agm/core package is compiled with es2015 module. As a result, it contains import and export in js code.

To remedy this you have two main options:

1. Compile @agm/core package to commonjs format.

You can use either babel or typescript to compile that package. Then you need to make sure you provided compiled version in your functions dependencies

functions/package.json

"dependencies": {
  ...
  "@agm/core": "file:./@agm/core"
},

Here I use local dependency but you can also use your own published version.

Another method would be compile directly in node_modules and publish the whole node_modules(I would avoid this):

firebase.json

{
  "functions": {
    "ignore": []
  }
}

How to compile?

Babel

Install dependencies in root directory.

npm i -D @babel/cli @babel/core @babel/preset-env

Use the following script to compile:

package.json

"postbuild": "babel node_modules/@agm/core -d functions/@agm/core --presets @babel/preset-env && node ./copy-package-json.js"

where

copy-package-json.js

const fs = require('fs-extra');
const { join } = require('path');

(async() => {
  await fs.copy(join(process.cwd(), 'node_modules/@agm/core/package.json'),
                join(process.cwd(), 'functions/@agm/core/package.json'));
})();

Typescript

tsconfig.agm.json

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "outDir": "./functions/@agm/core",
    "types": [],
    "module": "commonjs"
  },
  "include": [
    "node_modules/@agm/core"
  ]
}

package.json

"postbuild": "tsc -p tsconfig.agm.json --allowJs && node ./copy-package-json.js"

2. Generate server bundle

This is what Angular universal tutorial uses so I prefer this solution.

Also follow this quide

Simple steps:


1. Install global dependencies

I have installed:

2. Create a new Angular project

ng new angular-agm

3. Add Angular universal

ng add @nguniversal/express-engine --clientProject angular-agm

4. Update server.ts

Export the express app, then remove the call to listen and change index to index2.

import 'zone.js/dist/zone-node';
import {enableProdMode} from '@angular/core';
// Express Engine
import {ngExpressEngine} from '@nguniversal/express-engine';
// Import module map for lazy loading
import {provideModuleMap} from '@nguniversal/module-map-ngfactory-loader';

import * as express from 'express';
import {join} from 'path';
import * as path from 'path';

// Faster server renders w/ Prod mode (dev mode never needed)
enableProdMode();

// Express server
export const app = express();

// const PORT = process.env.PORT || 4000;
const DIST_FOLDER = join(process.cwd(), 'dist/browser');

const index = require('fs')
  .readFileSync(path.resolve(DIST_FOLDER, 'index2.html'), 'utf8')
  .toString();

const domino = require('domino');
const win = domino.createWindow(index);
global['window'] = win;
global['document'] = win.document;

// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const {AppServerModuleNgFactory, LAZY_MODULE_MAP} = require('./dist/server/main');

// Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
app.engine('html', ngExpressEngine({
  bootstrap: AppServerModuleNgFactory,
  providers: [
    provideModuleMap(LAZY_MODULE_MAP)
  ]
}));

app.set('view engine', 'html');
app.set('views', DIST_FOLDER);

// Example Express Rest API endpoints
// app.get('/api/**', (req, res) => { });
// Serve static files from /browser
app.get('*.*', express.static(DIST_FOLDER, {
  maxAge: '1y'
}));

// All regular routes use the Universal engine
app.get('*', (req, res) => {
  res.render('index2', { req });
});

// Start up the Node server
/*app.listen(PORT, () => {
  console.log(`Node Express server listening on http://localhost:${PORT}`);
});*/

5. Build

npm run build:ssr

where build:ssr

"build:ssr": "npm run build:client-and-server-bundles && npm run compile:server && node ./tools/copy-artifacts.js",

copy-artifacts.js

const fs = require('fs-extra');
const { join } = require('path');

(async() => {
  const src = join(process.cwd(), 'dist');
  const copy = join(process.cwd(), 'functions/dist');

  await fs.rename(join(src, 'browser/index.html'), join(src, 'browser/index2.html'));
  await fs.remove(copy);
  await fs.copy(src, copy);
})();

6. Update functions/index.js to use built version of express app

const functions = require('firebase-functions');

const { app } = require('./dist/server');

exports.ssr = functions.https.onRequest(app);

7. Configure firebase.json

{
  "hosting": {
    "public": "dist/browser",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "**",
        "function": "ssr"
      }
    ]
  }
}

The source code can be found on Github

See also demo https://angular-agm.firebaseapp.com/

Whistle answered 8/4, 2019 at 4:53 Comment(19)
Comments are not for extended discussion; this conversation has been moved to chat.Spectral
Currently the html doesnt render when deploy, but works locally using firebase serve. Any idea why?Elka
Did you manage to find out how to solve the AngularFire error for angular universal: "TypeError: app.auth is not a function"?Elka
Unfortunately, I don't have any reproducible example.Whistle
can you add angularfire and firebase to your github app and see if you get the same error @Whistle ?Elka
Hey, yes I will try it later.Whistle
I've added it to my repo github.com/alexzuza/angular-agm/commit/… Here's the live preview angular-agm.firebaseapp.comWhistle
Seems I need to add AngularFireAuthModule as well since the error comes from that module.Whistle
Yes, now I got it. I will take a look later.Whistle
I got it working for me. angular-agm.firebaseapp.com Please take a look at this commit. github.com/alexzuza/angular-agm/commit/… The key point is "engines": {"node": "8"}, Another way that worked is to upload the whole node_modules to the functions. Note that we do not even need @firebase/app dependency in functions/package.jsonWhistle
@Whistle could the issue be with the webpack config and the regex: const regex = /firebase\/(app|firestore)/; for externals?Elka
??? Does "engines": {"node": "8"} work for you? We configured externals since we got others errors which related to wrong bundle proccess for firebase-sdk. It couldn't work with nodejs environment directly. Exposing it as externals we said that the nodejs should process firebase deps itself. The problem is that firebase functions hosting can't correctly install dependencies if node version is 6. If you push the whole node_modules then it will also workWhistle
should i add "engines": {"node": "8"} to webpack config?Elka
Looks like you didn't see my commit github.com/alexzuza/angular-agm/blob/…Whistle
It works! only issue is the firestore page breaks when viewing source: behired-staging.firebaseapp.com/job-boardElka
I think you need to create another issue on this since we went so far from the original Angular Google Map issueWhistle
@Whistle im going to give you the 500 now and make a new question for the firestore issue with ssr. Thanks for your help. been amazing! will you help me with the firestore data issue ? il post the link to the question in the commentsElka
@Whistle I've got one more issue related to this question regarding firestore database not rendering for ssr: could you take a look please: #55634599Elka
@Whistle I've noticed that with ssr when I try jump to a page in the address bar e.g: behired.co.uk/blog it takes a long time to load. Whats the reason for this? I thought ssr would be fast rendering of the html. But it seems to take a long time to load pages?Elka

© 2022 - 2024 — McMap. All rights reserved.