Skip to content

Commit

Permalink
refactor(http): warn when HttpClient doesn't use fetch during SSR
Browse files Browse the repository at this point in the history
This commit adds a logic to produce a warning in case HttpClient doesn't use fetch during SSR.
It's recommended to use `fetch` for performance and compatibility reasons.
  • Loading branch information
AndrewKushnir committed Oct 4, 2023
1 parent 6441de8 commit a7569b1
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 4 deletions.
1 change: 1 addition & 0 deletions packages/common/http/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@
*/
export const enum RuntimeErrorCode {
MISSING_JSONP_MODULE = -2800,
NOT_USING_FETCH_BACKEND_IN_SSR = 2801,
}
30 changes: 29 additions & 1 deletion packages/common/http/src/interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@
* found in the LICENSE file at https://angular.io/license
*/

import {EnvironmentInjector, inject, Injectable, InjectionToken, ɵInitialRenderPendingTasks as InitialRenderPendingTasks} from '@angular/core';
import {isPlatformServer} from '@angular/common';
import {EnvironmentInjector, inject, Injectable, InjectionToken, PLATFORM_ID, ɵformatRuntimeError as formatRuntimeError, ɵInitialRenderPendingTasks as InitialRenderPendingTasks} from '@angular/core';
import {Observable} from 'rxjs';
import {finalize} from 'rxjs/operators';

import {HttpBackend, HttpHandler} from './backend';
import {RuntimeErrorCode} from './errors';
import {FetchBackend} from './fetch';
import {HttpRequest} from './request';
import {HttpEvent} from './response';

Expand Down Expand Up @@ -220,6 +223,13 @@ export function legacyInterceptorFnFactory(): HttpInterceptorFn {
};
}

let fetchBackendWarningDisplayed = false;

/** Internal function to reset the flag in tests */
export function resetFetchBackendWarningFlag() {
fetchBackendWarningDisplayed = false;
}

@Injectable()
export class HttpInterceptorHandler extends HttpHandler {
private chain: ChainedInterceptorFn<unknown>|null = null;
Expand All @@ -233,6 +243,24 @@ export class HttpInterceptorHandler extends HttpHandler {
// is used.
const primaryHttpBackend = inject(PRIMARY_HTTP_BACKEND, {optional: true});
this.backend = primaryHttpBackend ?? backend;

// We strongly recommend using fetch backend for HTTP calls when SSR is used
// for an application. The logic below checks if that's the case and produces
// a warning otherwise.
if ((typeof ngDevMode === 'undefined' || ngDevMode) && !fetchBackendWarningDisplayed) {
const isServer = isPlatformServer(injector.get(PLATFORM_ID));
if (isServer && !(this.backend instanceof FetchBackend)) {
fetchBackendWarningDisplayed = true;
console.warn(formatRuntimeError(
RuntimeErrorCode.NOT_USING_FETCH_BACKEND_IN_SSR,
'Angular detected that `HttpClient` is not configured ' +
'to use `fetch` APIs. It\'s strongly recommended to ' +
'enable `fetch` for applications that use Server-Side Rendering ' +
'for better performance and compatibility. ' +
'To enable `fetch`, add the `withFetch()` to the `provideHttpClient()` ' +
'call at the root of the application.'));
}
}
}

override handle(initialRequest: HttpRequest<any>): Observable<HttpEvent<any>> {
Expand Down
60 changes: 57 additions & 3 deletions packages/common/http/test/provider_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ import {createEnvironmentInjector, EnvironmentInjector, inject, InjectionToken,
import {TestBed} from '@angular/core/testing';
import {EMPTY, Observable} from 'rxjs';

import {HttpInterceptorFn} from '../src/interceptor';
import {HttpInterceptorFn, resetFetchBackendWarningFlag} from '../src/interceptor';
import {provideHttpClient, withFetch, withInterceptors, withInterceptorsFromDi, withJsonpSupport, withNoXsrfProtection, withRequestsMadeViaParent, withXsrfConfiguration} from '../src/provider';

describe('provideHttp', () => {
describe('provideHttpClient', () => {
beforeEach(() => {
setCookie('');
TestBed.resetTestingModule();
Expand Down Expand Up @@ -390,10 +390,23 @@ describe('provideHttp', () => {

describe('fetch support', () => {
it('withFetch', () => {
const consoleWarnSpy = spyOn(console, 'warn');

TestBed.resetTestingModule();
TestBed.configureTestingModule({providers: [provideHttpClient(withFetch())]});
TestBed.configureTestingModule({
providers: [
// Setting this flag to verify that there are no
// `console.warn` produced for cases when `fetch`
// is enabled and we are running in a browser.
{provide: PLATFORM_ID, useValue: 'browser'},
provideHttpClient(withFetch()),
]
});
const fetchBackend = TestBed.inject(HttpBackend);
expect(fetchBackend).toBeInstanceOf(FetchBackend);

// Make sure there are no warnings produced.
expect(consoleWarnSpy.calls.count()).toBe(0);
});

it('withFetch should always override the backend', () => {
Expand All @@ -411,6 +424,47 @@ describe('provideHttp', () => {
const handler = TestBed.inject(HttpHandler);
expect((handler as any).backend).toBeInstanceOf(FetchBackend);
});

it('should not warn if fetch is not configured when running in a browser', () => {
resetFetchBackendWarningFlag();

TestBed.resetTestingModule();
TestBed.configureTestingModule({
providers: [
// Setting this flag to verify that there are no
// `console.warn` produced for cases when `fetch`
// is enabled and we are running in a browser.
{provide: PLATFORM_ID, useValue: 'browser'},
provideHttpClient(),
]
});

const consoleWarnSpy = spyOn(console, 'warn');

TestBed.inject(HttpHandler);

expect(consoleWarnSpy.calls.count()).toBe(0);
});

it('should warn during SSR if fetch is not configured', () => {
resetFetchBackendWarningFlag();

TestBed.resetTestingModule();
TestBed.configureTestingModule({
providers: [
provideHttpClient(),
]
});

const consoleWarnSpy = spyOn(console, 'warn');

TestBed.inject(HttpHandler);

expect(consoleWarnSpy.calls.count()).toBe(1);
expect(consoleWarnSpy.calls.argsFor(0)[0])
.toContain(
'NG02801: Angular detected that `HttpClient` is not configured to use `fetch` APIs.');
});
});
});

Expand Down

0 comments on commit a7569b1

Please sign in to comment.