Sage-Bionetworks / sage-monorepo

Where OpenChallenges, Schematic, and other Sage open source apps are built
https://sage-bionetworks.github.io/sage-monorepo/
Apache License 2.0
21 stars 12 forks source link

[Story] Migrate OC SSR to Angular v16 #1994

Closed tschaffter closed 10 months ago

tschaffter commented 11 months ago

What projects is this story for?

OpenChallenges

As a user, I want

NA

Description

https://nx.dev/packages/angular/generators/setup-ssr

Acceptance criteria

No response

Tasks

No response

Anything else?

No response

Have you linked this story to a GitHub Project?

tschaffter commented 10 months ago

Update 2023-08-31

The migration started but couldn't not be completed this sprint. There is another issue I should solve first about the loading of the app config file which appeared after migrating to standalone components.

tschaffter commented 10 months ago

Protocol

Setup SSR for the project openchallenges-app:

$ nx generate @nx/angular:setup-ssr --project openchallenges-app --serverPort 4200 --standalone

>  NX  Generating @nx/angular:setup-ssr

UPDATE apps/openchallenges/app/project.json
UPDATE nx.json
UPDATE apps/openchallenges/app/tsconfig.server.json
CREATE apps/openchallenges/app/server.ts
UPDATE apps/openchallenges/app/src/main.server.ts
CREATE apps/openchallenges/app/src/app/app.config.server.ts

Note I wanted to use the option --hydration but the generator does not recognize it despite using Ng 16.7.2 (>= 16.0.0.).

Start the SSR server:

nx serve-ssr openchallenges-app

Open your browser and access localhost:4200 or whatever server port was specified. Here I bumped again into this issue. Initially, it was because Keycloak needed window, which I could only get to work by no longer initializing KC (see this comment). Here, the issue is because of the new library that we use for rendering plots that needs window:

ReferenceError: window is not defined
    at ngOnDestroy (./node_modules/ngx-echarts/fesm2022/ngx-echarts.mjs:135:9)
    at executeOnDestroys (./node_modules/@angular/core/fesm2022/core.mjs:7275:32)
    at cleanUpView (./node_modules/@angular/core/fesm2022/core.mjs:7183:9)
    at destroyViewTree (./node_modules/@angular/core/fesm2022/core.mjs:7018:17)
    at destroyLView (./node_modules/@angular/core/fesm2022/core.mjs:7161:9)
    at destroy (./node_modules/@angular/core/fesm2022/core.mjs:13138:9)
    at view (./node_modules/@angular/core/fesm2022/core.mjs:28644:56)
    at Array.forEach (<anonymous>)
    at ngOnDestroy (./node_modules/@angular/core/fesm2022/core.mjs:28644:33)
    at destroy (./node_modules/@angular/core/fesm2022/core.mjs:9243:25)

Reading the docs: https://echarts.apache.org/en/tutorial.html#Server-side%20Rendering

Temporarily commenting out the code related to the library ngx-echarts.

The next error is that the server can't fetch data from OC REST API.

ERROR HttpErrorResponse {
  headers: HttpHeaders {
    normalizedNames: Map(0) {},
    lazyUpdate: null,
    headers: Map(0) {}
  },
  status: 0,
  statusText: 'Unknown Error',
  url: 'http://localhost/v1/organizations',
  ok: false,
  name: 'HttpErrorResponse',
  message: 'Http failure response for http://localhost/v1/organizations: 0 Unknown Error',
  error: ProgressEvent {
    type: 'error',
    target: XMLHttpRequest {
      onloadstart: null,
      onprogress: null,
      onabort: null,
      onerror: null,
      onload: null,
      ontimeout: null,
      onloadend: null,
      _listeners: [Object],
      onreadystatechange: null,
      _anonymous: undefined,
      readyState: 4,
      response: null,
      responseText: '',
      responseType: 'text',
      responseURL: '',
      status: 0,
      statusText: '',
      timeout: 0,
      upload: [XMLHttpRequestUpload],
      _method: 'GET',
      _url: [Url],
      _sync: false,
      _headers: [Object],
      _loweredHeaders: [Object],
      _mimeOverride: null,
      _request: null,
      _response: null,
      _responseParts: null,
      _responseHeaders: null,
      _aborting: null,
      _error: null,
      _loadedBytes: 0,
      _totalBytes: 0,
      _lengthComputable: false
    },
    currentTarget: XMLHttpRequest {
      onloadstart: null,
      onprogress: null,
      onabort: null,
      onerror: null,
      onload: null,
      ontimeout: null,
      onloadend: null,
      _listeners: [Object],
      onreadystatechange: null,
      _anonymous: undefined,
      readyState: 4,
      response: null,
      responseText: '',
      responseType: 'text',
      responseURL: '',
      status: 0,
      statusText: '',
      timeout: 0,
      upload: [XMLHttpRequestUpload],
      _method: 'GET',
      _url: [Url],
      _sync: false,
      _headers: [Object],
      _loweredHeaders: [Object],
      _mimeOverride: null,
      _request: null,
      _response: null,
      _responseParts: null,
      _responseHeaders: null,
      _aborting: null,
      _error: null,
      _loadedBytes: 0,
      _totalBytes: 0,
      _lengthComputable: false
    },
    lengthComputable: false,
    loaded: 0,
    total: 0
  }
}

The above error was because the server couldn't load the config file.

tschaffter commented 10 months ago

Part 2

The home page has issues but the challenge search page and other seem to work.

Issues:

I realize that the following error is triggered by Google Tag Manager instruction in app.component.ts:

this.gtmService.pushTag(gtmTag);

Error:

ERROR Error: Uncaught (in promise): [object Undefined]
    at resolvePromise (./node_modules/zone.js/dist/zone-node.js:1179:35)
    at apply (./node_modules/zone.js/dist/zone-node.js:1086:21)
    at reject (./node_modules/zone.js/dist/zone-node.js:1102:37)
    at apply (./node_modules/angular-google-tag-manager/fesm2022/angular-google-tag-manager.mjs:131:34)
    at invoke (./node_modules/zone.js/dist/zone-node.js:412:30)
    at onInvoke (./node_modules/@angular/core/fesm2022/core.mjs:27450:33)
    at invoke (./node_modules/zone.js/dist/zone-node.js:411:56)
    at run (./node_modules/zone.js/dist/zone-node.js:166:47)
    at apply (./node_modules/zone.js/dist/zone-node.js:1243:38)
    at _ZoneDelegate._ZoneDelegate.invokeTask (./node_modules/zone.js/dist/zone-node.js:446:35) {
  rejection: undefined,
  promise: ZoneAwarePromise [Promise] {
    __zone_symbol__state: 0,
    __zone_symbol__value: undefined
  },
  zone: <ref *1> Zone {
    _parent: Zone {
      _parent: [Zone],
      _name: 'asyncStackTagging for Angular',
      _properties: {},
      _zoneDelegate: [_ZoneDelegate]
    },
    _name: 'angular',
    _properties: { isAngularZone: true },
    _zoneDelegate: <ref *2> _ZoneDelegate {
      _taskCounts: [Object],
      zone: [Circular *1],
      _parentDelegate: [_ZoneDelegate],
      _forkZS: null,
      _forkDlgt: null,
      _forkCurrZone: null,
      _interceptZS: null,
      _interceptDlgt: null,
      _interceptCurrZone: null,
      _invokeZS: [Object],
      _invokeDlgt: [_ZoneDelegate],
      _invokeCurrZone: [Circular *1],
      _handleErrorZS: [Object],
      _handleErrorDlgt: [_ZoneDelegate],
      _handleErrorCurrZone: [Circular *1],
      _scheduleTaskZS: [Object],
      _scheduleTaskDlgt: [_ZoneDelegate],
      _scheduleTaskCurrZone: [Circular *1],
      _invokeTaskZS: [Object],
      _invokeTaskDlgt: [_ZoneDelegate],
      _invokeTaskCurrZone: [Circular *1],
      _cancelTaskZS: [Object],
      _cancelTaskDlgt: [_ZoneDelegate],
      _cancelTaskCurrZone: [Circular *1],
      _hasTaskZS: [Object],
      _hasTaskDlgt: [_ZoneDelegate],
      _hasTaskDlgtOwner: [Circular *2],
      _hasTaskCurrZone: [Circular *1]
    }
  },
  task: ZoneTask {
    _zone: <ref *1> Zone {
      _parent: [Zone],
      _name: 'angular',
      _properties: [Object],
      _zoneDelegate: [_ZoneDelegate]
    },
    runCount: 0,
    _zoneDelegates: null,
    _state: 'notScheduled',
    type: 'microTask',
    source: 'Promise.then',
    data: ZoneAwarePromise [Promise] {
      __zone_symbol__state: true,
      __zone_symbol__value: undefined
    },
    scheduleFn: undefined,
    cancelFn: undefined,
    callback: [Function (anonymous)],
    invoke: [Function (anonymous)],
    consoleTask: null
  }
}
tschaffter commented 10 months ago

Part 3

The browser shows this warning:

Loading failed for the <script> with source “http://localhost:4200/browser-sync/browser-sync-client.js?v=2.29.3”.

But the script can be accessed manually:

image

UPDATE: This error may be because of cache poisoning: starting fresh in an incognito page does not show any warnings.

tschaffter commented 10 months ago

With SSR:

Warning: /workspaces/sage-monorepo/libs/openchallenges/challenge-search/src/lib/challenge-search.component.ts depends on 'lodash'. CommonJS or AMD dependencies can cause optimization bailouts.
tschaffter commented 10 months ago

Missing main.js

The Agora SSR page reacts to user interaction. The source of this page includes the following code:

<script src="[runtime.js](view-source:http://localhost:4200/runtime.js)" type="module"></script><script src="[polyfills.js](view-source:http://localhost:4200/polyfills.js)" type="module"></script><script src="[vendor.js](view-source:http://localhost:4200/vendor.js)" type="module"></script><script src="[main.js](view-source:http://localhost:4200/main.js)" type="module"></script>

</body></html>

But is code is missing from the source page of OC SSR, which would explain why JS code is not running.

Note that the browser folder of OC does include main.js.

Solution

The issue is because I used a self-closing tag in index.html:

<openchallenges-root />

Restoring this line to:

<openchallenges-root></openchallenges-root>

solves the issue and the the SSR page now load successfully. It's slow but I hope that using a production server will speed up the delivery.