Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Angular ssr compilation issue #1455

Closed
yugank1991 opened this issue Jan 7, 2019 · 19 comments
Closed

Angular ssr compilation issue #1455

yugank1991 opened this issue Jan 7, 2019 · 19 comments

Comments

@yugank1991
Copy link

I have integrated Angular 7.0, Angular Universal 7.0.2 and @angular/fire 5.1.1 .

I am able to run the project using ng build command .

When I am trying to compile it and deploy it on firebase functions . I am getting error as below

TypeError: Cannot read property 'stringify' of undefined

I dig into issue and I found that @firebase/webchannel-wrapper index.js file has a code as below

var ub=h.JSON.stringify;

and due to this I am not able to deploy the application.

Hope, you understand my issue and will get quick reply for this.

Thanks.

@google-oss-bot
Copy link
Contributor

I found a few problems with this issue:

  • I couldn't figure out how to label this issue, so I've labeled it for a human to triage. Hang tight.
  • This issue does not seem to follow the issue template. Make sure you provide all the required information.

@Feiyang1
Copy link
Member

Feiyang1 commented Jan 7, 2019

I don't quite understand your use case here. What do you mean by deploying to firebase functions? I assume you are making a web app that runs in browsers, then I don't understand what you are deploying to firebase function. Can you please clarify?

@langhoangal
Copy link

langhoangal commented Jan 17, 2019

Hi @Feiyang1 ,

I think he got into the same issue with me. As i posted a question on stackoverflow at this link.

When trying with angular severside rendering, at step 5 it shows that "TypeError: Cannot read property 'stringify' of undefined" and after take a look on the trace it show that the error comes from '/node_modules/@firebase/webchannel-wrapper/dist/index.esm.js'

Could you help to give a look?

@Feiyang1
Copy link
Member

Okay. I think I know what's going on here - the browser bundle of firestore is included in your server bundle that runs in node. I couldn't reproduce the exact same error that you saw, but I think it is it.

Basically when you bundle your server code with webpack, it prioritizes the module script over the main script. Firesbase defines both module script and main script, and the module script is packaged specifically for the browser use case, so it fails when you run it in node.

The workaround is to exclude firestore in the server bundle and load it dynamically in the runtime. Add externals configuration in your webpack config file that you use for the server build (e.g. webpack.server.config.js):

// change the regex to include the packages you want to exclude
const regex = /firebase\/(app|firestore)/;

// copied from the Angular SSR tutorial
module.exports = {
  entry: { server: './server.ts' },
  resolve: { extensions: ['.js', '.ts'] },
  target: 'node',
  mode: 'none',
  // this makes sure we include node_modules and other 3rd party libraries
  externals: [/node_modules/, function(context, request, callback) {

    // exclude firebase products from being bundled, so they will be loaded using require() at runtime.
    if(regex.test(request)) {
      return callback(null, 'commonjs ' + request);
    }
    callback();
  }],
....
};

Hope it helps!

@michelepatrassi
Copy link

@Feiyang1 thank you so much for you solution, works like a charm!

I will provide my context and what I did to solve the error, so maybe can help someone encountering the same issue.
My goal was to use firebaseui in my angular ssr app, so I picked up firebaseui-angular as a wrapper. Unfortunately, firebaseui does not support SSR and then also the wrapper. This resulted in window is not defined error, that I solved like suggested here:

const domino = require('domino');
const fs = require('fs');
const path = require('path');
const template = fs.readFileSync(path.join(__dirname, '.', 'dist', 'index.html')).toString();
const win = domino.createWindow(template);
global['window'] = win;
global['document'] = win.document;

This solved the window error, but then I encountered the TypeError: Cannot read property 'stringify' of undefined error mentioned in this issue. Updating the webpack.server.config as suggested from @Feiyang1 solved this error.

Then I had some problems in my app with the following solutions:

  • firebaseui-angular with its component don't work on the server => I simply render the component only if the platform is browser.
  • on the server, the authState is always null => I postpone the check, for example in a guard, until the platform is browser. Here's an example of a guard that I have for the account area
  canActivateChild(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
      return this.afAuth.authState.pipe(
        tap(authState => {
          if (!this.platformService.isBrowser()) {
            this.logger.warn(`[AccountGuard] Blocking guard in server: authState is always null:`, authState);
          }
        }),
        filter(_ => this.platformService.isBrowser()),
        tap(authState => this.logger.warn(`[AccountGuard] Now on the browser, checking authState...`, authState)),
        switchMap(authState => {
          if (authState) {
            return of(true);
          }

          this.logger.warn(`No permission to access account module! Redirecting to "${this.redirectPath}"`);
          this.router.navigate([this.redirectPath]);
          return of(false);
        })
      );
  }

where the platformService related code is the following:

  constructor(
    @Inject(PLATFORM_ID) private platformId: Object,
    @Inject(APP_ID) private appId: string,
    private logger: NGXLogger
  ) { }

  isBrowser(): boolean {
    const isBrowserPlatform = isPlatformBrowser(this.platformId);
    const platform = isBrowserPlatform ? 'in the browser' : 'on the server';
    const logMessage = `Running ${platform} with appId=${this.appId}`;

    if (isBrowserPlatform) {
      this.logger.info(logMessage);
    } else {
      console.log(logMessage); // output on node console for debug
    }

    return isBrowserPlatform;
  }

Hope it helps everyone struggling with the same issue! Enjoy 🥳

@langhoangal
Copy link

Thank you for your help @Feiyang1
@Iar0 glad that it help you also. I just want to add some more info for people who got into the same issue.

The instruction in this link seems not updated, because the code in the provided repo is quite diffirent and more important that it works even with firestore without any new config (the way @Feiyang1 point out)

I did clone the repo and then tried to add firestore on it and everything just worked properly. So, now you have two choices: go with the instruction and apply the fix from @Feiyang1 OR just clone the repo and checkout the way they implement SSR.

Thanks!

@michelepatrassi
Copy link

michelepatrassi commented Jan 22, 2019

@langhoangal do you mean the code downloaded clicking "Download the finished sample code"? Did you figured out what was different? From a quick look the code looks equal (checked the webpack and server config)

@langhoangal
Copy link

@Iar0 i mean the Uversal-starter at this link.

@michelepatrassi
Copy link

@langhoangal thx! I checked out the repo and made a comparison: the main differences are the compilation using tsc instead of webpack and some tsconfigs options.

Removing the webpack usage popped out errors because of issues with libraries like Nolanus/ngx-page-scroll#212 not commonjs compatible. Looking at angular/universal-starter#626 (comment) looks like they also reverted the removal of webpack because of the ecosystem.

I'll personally stick with @Feiyang1 workaround for now until libraries enable more support for SSR

@TP18
Copy link

TP18 commented May 11, 2019

Does anybody know how to resolve this with VueJS Unit:Testing with Mocha

MOCHA  Testing...

 RUNTIME EXCEPTION  Exception occurred while loading your tests

TypeError: Cannot read property 'stringify' of undefined
    at Module../node_modules/@firebase/webchannel-wrapper/dist/index.esm.js (/Users/TP/tp-health-test/dist/webpack:/node_modules/@firebase/webchannel-wrapper/dist/index.esm.js:21:1)
    at __webpack_require__ (/Users/TP/tp-health-test/dist/webpack:/webpack/bootstrap:25:1)
    at Module../node_modules/@firebase/firestore/dist/index.esm.js (/Users/TP/tp-health-test/dist/webpack:/node_modules/@firebase/firestore/dist/index.esm.js:1:1)
    at __webpack_require__ (/Users/TP/tp-health-test/dist/webpack:/webpack/bootstrap:25:1)
    at Module../node_modules/firebase/firestore/dist/index.esm.js (/Users/TP/tp-health-test/dist/webpack:/node_modules/firebase/firestore/dist/index.esm.js:1:1)

Warning: This is a browser-targeted Firebase bundle but it appears it is being
run in a Node environment. If running in a Node environment, make sure you
are using the bundle specified by the “main” field in package.json.

If you are using Webpack, you can specify “main” as the first item in
“resolve.mainFields”:

@canliozkan
Copy link

Hi, Thank you in advance for your answers.
I got the same mistake. I implemented @Feiyang1 solution. "TypeError: Cannot read property 'stringify' of undefined" error resolved. After importing AngularFirestoreModule in app.module.ts, the following error occurred during ssr.

/Users/v3zir/Documents/project/angular/angular-universal-starter/node_modules/protobufjs/src/root.js:234
throw Error("not supported");
^
Error: not supported
at Root.loadSync (/Users/ozkancanli/Documents/project/angular/angular-universal-starter/node_modules/protobufjs/src/root.js:234:15)
at Object.loadSync (/Users/ozkancanli/Documents/project/angular/angular-universal-starter/node_modules/protobufjs/src/index-light.js:69:17)
at Object. (/Users/ozkancanli/Documents/project/angular/angular-universal-starter/node_modules/@grpc/proto-loader/build/src/index.js:244:37)

@jwoodmansey
Copy link

@v3zir I can replicate this.
It's coming from grpc which includes protobufjs `Root.prototype.loadSync``

Locally changing node_modules/@grpc/proto-loader/node_modules/protobufjs/src/util/minimal.js line 55 to util.isNode = true gets around the error and I can successfully communicate with firestore but I have no idea how to properly fix this.

@hbk899
Copy link

hbk899 commented Jul 20, 2019

@Feiyang1 thank you so much for you solution, works like a charm!

I will provide my context and what I did to solve the error, so maybe can help someone encountering the same issue.
My goal was to use firebaseui in my angular ssr app, so I picked up firebaseui-angular as a wrapper. Unfortunately, firebaseui does not support SSR and then also the wrapper. This resulted in window is not defined error, that I solved like suggested here:

const domino = require('domino');
const fs = require('fs');
const path = require('path');
const template = fs.readFileSync(path.join(__dirname, '.', 'dist', 'index.html')).toString();
const win = domino.createWindow(template);
global['window'] = win;
global['document'] = win.document;

This solved the window error, but then I encountered the TypeError: Cannot read property 'stringify' of undefined error mentioned in this issue. Updating the webpack.server.config as suggested from @Feiyang1 solved this error.

Then I had some problems in my app with the following solutions:

  • firebaseui-angular with its component don't work on the server => I simply render the component only if the platform is browser.
  • on the server, the authState is always null => I postpone the check, for example in a guard, until the platform is browser. Here's an example of a guard that I have for the account area
  canActivateChild(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
      return this.afAuth.authState.pipe(
        tap(authState => {
          if (!this.platformService.isBrowser()) {
            this.logger.warn(`[AccountGuard] Blocking guard in server: authState is always null:`, authState);
          }
        }),
        filter(_ => this.platformService.isBrowser()),
        tap(authState => this.logger.warn(`[AccountGuard] Now on the browser, checking authState...`, authState)),
        switchMap(authState => {
          if (authState) {
            return of(true);
          }

          this.logger.warn(`No permission to access account module! Redirecting to "${this.redirectPath}"`);
          this.router.navigate([this.redirectPath]);
          return of(false);
        })
      );
  }

where the platformService related code is the following:

  constructor(
    @Inject(PLATFORM_ID) private platformId: Object,
    @Inject(APP_ID) private appId: string,
    private logger: NGXLogger
  ) { }

  isBrowser(): boolean {
    const isBrowserPlatform = isPlatformBrowser(this.platformId);
    const platform = isBrowserPlatform ? 'in the browser' : 'on the server';
    const logMessage = `Running ${platform} with appId=${this.appId}`;

    if (isBrowserPlatform) {
      this.logger.info(logMessage);
    } else {
      console.log(logMessage); // output on node console for debug
    }

    return isBrowserPlatform;
  }

Hope it helps everyone struggling with the same issue! Enjoy 🥳

I tried to solve window issue as you mention then i get this error..
.........................................................................
throw err;
^

Error: ENOENT: no such file or directory, open '\dist\index.html'
...........................................................................................................
As my index.html is in dist/browser so i tried to do it like
const template = fs.readFileSync(path.join(__dirname, '.', 'dist/browser', 'index.html')).toString();

still got this error
fs.js:114
throw err;
^

Error: ENOENT: no such file or directory, open '\dist\index.html'

@RahulGuptaIIITA
Copy link

hello All, I tried all the suggested methods but still getting the error. Though i'm getting
**
/dist/server.js:300714
var XMLHttpRequestPrototype = XMLHttpRequest.prototype;
^

ReferenceError: XMLHttpRequest is not defined
**

please help.

@hbk899
Copy link

hbk899 commented Jul 22, 2019

@v3zir I can replicate this.
It's coming from grpc which includes protobufjs `Root.prototype.loadSync``

Locally changing node_modules/@grpc/proto-loader/node_modules/protobufjs/src/util/minimal.js line 55 to util.isNode = true gets around the error and I can successfully communicate with firestore but I have no idea how to properly fix this.

I get this error on applying window solution can u help with it

Error: not supported
at Root.loadSync (C:\Users\Bilal Khalid\Desktop\codes\seo\seoforum\node_modules\protobufjs\src\root.js:234:15)
at Object.loadSync (C:\Users\Bilal Khalid\Desktop\codes\seo\seoforum\node_modules\protobufjs\src\index-light.js:69:1

@Feiyang1
Copy link
Member

You are not likely getting help by replying to a closed issue. It's also hard to have a focused conversation with so many different issues.

Please open a new issue if you are facing problems.

@danieldanielecki
Copy link

@jwoodmansey your solution works, but more precisely:

  1. Open node_modules/protobufjs/src/util/minimal.js (it's NOT inside the @grpc package).
  2. Change the following line util.isNode = Boolean(util.global.process && util.global.process.versions && util.global.process.versions.node); to util.isNode = true;

Super nasty bug, something should be done about this and the topic opened again. Basically I got this with the @angular/fire package.

Environment:

"@angular/fire": "^5.2.1",
"firebase": "^6.3.1",
"firebase-admin": "^8.3.0",
"firebase-functions": "^3.2.0"

Now Angular Universal (SSR) works again,

@danieldanielecki
Copy link

danieldanielecki commented Jul 26, 2019

The problem is... how to deploy it? On localhost it works fine, but when I hit firebase deploy ending up with:

Function failed on loading user code. Error message: Provided module can't be loaded.
Is there a syntax error in your code?
Detailed stack trace: Error: not supported
    at Root.loadSync (/srv/functions/node_modules/protobufjs/src/root.js:234:15)
    at Object.loadSync (/srv/functions/node_modules/protobufjs/src/index-light.js:69:17)
    at Object.<anonymous> (/srv/functions/node_modules/@grpc/proto-loader/build/src/index.js:244:37)
    at Module._compile (internal/modules/cjs/loader.js:701:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:712:10)
    at Module.load (internal/modules/cjs/loader.js:600:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:539:12)
    at Function.Module._load (internal/modules/cjs/loader.js:531:3)
    at Module.require (internal/modules/cjs/loader.js:637:17)
    at require (internal/modules/cjs/helpers.js:22:18)
Could not load the function, shutting down.

Edit, I was able to make it that way:

  1. Make a change in the protobufjs dependency in your node_modules.
  2. Copy this package protobufjs from your node_modules to functions folder.
  3. Add to your dependencies the following: "protobufjs": "file:./protobufjs" (assuming you placed the protobufjs folder in the root of functions folder).
  4. Run: firebase deploy.

Still, hope so it'll be resolved with the @angular/fire.

@danieldanielecki
Copy link

The problem is... how to deploy it? On localhost it works fine, but when I hit firebase deploy ending up with:

Function failed on loading user code. Error message: Provided module can't be loaded.
Is there a syntax error in your code?
Detailed stack trace: Error: not supported
    at Root.loadSync (/srv/functions/node_modules/protobufjs/src/root.js:234:15)
    at Object.loadSync (/srv/functions/node_modules/protobufjs/src/index-light.js:69:17)
    at Object.<anonymous> (/srv/functions/node_modules/@grpc/proto-loader/build/src/index.js:244:37)
    at Module._compile (internal/modules/cjs/loader.js:701:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:712:10)
    at Module.load (internal/modules/cjs/loader.js:600:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:539:12)
    at Function.Module._load (internal/modules/cjs/loader.js:531:3)
    at Module.require (internal/modules/cjs/loader.js:637:17)
    at require (internal/modules/cjs/helpers.js:22:18)
Could not load the function, shutting down.

Edit, I was able to make it that way:

1. Make a change in the `protobufjs` dependency  in your `node_modules`.

2. Copy this package `protobufjs` from your `node_modules` to `functions` folder.

3. Add to your `dependencies` the following: `"protobufjs": "file:./protobufjs"` (assuming you placed the `protobufjs` folder in the root of functions folder).

4. Run: `firebase deploy`.

Still, hope so it'll be resolved with the @angular/fire.

My final solution was to change this simple line, and upload it to a separated repo (https://github.com/danieldanielecki/protobufjs-angularfire). Then in package.json do that way "protobufjs": "https://github.com/danieldanielecki/protobufjs-angularfire". You can of course upload it on your GitHub, and link it from there.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests