mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fb65ad157c
The `getBaseHref` method is called several times per request and currently queries through the entire document. We can speed it up by taking advantage of the fact that the `<base>` can only be a direct child of the `<head>` and is usually defined towards the beginning. Below are some benchmarks for a "Hello world" app before and after this change. ### Before: ``` Running 60s test @ http://localhost:4202 100 connections with 10 pipelining factor ┌─────────┬────────┬────────┬────────┬────────┬───────────┬──────────┬─────────┐ │ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ ├─────────┼────────┼────────┼────────┼────────┼───────────┼──────────┼─────────┤ │ Latency │ 568 ms │ 853 ms │ 901 ms │ 904 ms │ 866.58 ms │ 437.6 ms │ 9915 ms │ └─────────┴────────┴────────┴────────┴────────┴───────────┴──────────┴─────────┘ ┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐ │ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ ├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤ │ Req/Sec │ 490 │ 826 │ 1,006 │ 1,643 │ 1,129.3 │ 234.69 │ 490 │ ├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤ │ Bytes/Sec │ 10.4 MB │ 17.4 MB │ 21.3 MB │ 34.7 MB │ 23.9 MB │ 4.96 MB │ 10.3 MB │ └───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘ Req/Bytes counts sampled once per second. # of samples: 60 69k requests in 60.04s, 1.43 GB read 90 errors (90 timeouts) ``` ### After ``` Running 60s test @ http://localhost:4202 100 connections with 10 pipelining factor ┌─────────┬────────┬────────┬────────┬─────────┬───────────┬───────────┬─────────┐ │ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ ├─────────┼────────┼────────┼────────┼─────────┼───────────┼───────────┼─────────┤ │ Latency │ 471 ms │ 831 ms │ 889 ms │ 1668 ms │ 835.91 ms │ 467.89 ms │ 9720 ms │ └─────────┴────────┴────────┴────────┴─────────┴───────────┴───────────┴─────────┘ ┌───────────┬─────────┬─────────┬─────────┬─────────┬──────────┬────────┬─────────┐ │ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ ├───────────┼─────────┼─────────┼─────────┼─────────┼──────────┼────────┼─────────┤ │ Req/Sec │ 390 │ 860 │ 1,145 │ 1,572 │ 1,156.77 │ 222.65 │ 390 │ ├───────────┼─────────┼─────────┼─────────┼─────────┼──────────┼────────┼─────────┤ │ Bytes/Sec │ 8.24 MB │ 18.2 MB │ 24.2 MB │ 33.2 MB │ 24.4 MB │ 4.7 MB │ 8.24 MB │ └───────────┴─────────┴─────────┴─────────┴─────────┴──────────┴────────┴─────────┘ Req/Bytes counts sampled once per second. # of samples: 60 71k requests in 60.03s, 1.47 GB read 140 errors (140 timeouts) ``` PR Close #61392
121 lines
3.5 KiB
TypeScript
121 lines
3.5 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google LLC All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.dev/license
|
|
*/
|
|
|
|
import {ɵsetRootDomAdapter as setRootDomAdapter} from '@angular/common';
|
|
import {ɵBrowserDomAdapter as BrowserDomAdapter} from '@angular/platform-browser';
|
|
|
|
import domino from './bundled-domino';
|
|
|
|
export function setDomTypes() {
|
|
// Make all Domino types available in the global env.
|
|
// NB: Any changes here should also be done in `packages/platform-server/init/src/shims.ts`.
|
|
Object.assign(globalThis, domino.impl);
|
|
(globalThis as any)['KeyboardEvent'] = domino.impl.Event;
|
|
}
|
|
|
|
/**
|
|
* Parses a document string to a Document object.
|
|
*/
|
|
export function parseDocument(html: string, url = '/') {
|
|
let window = domino.createWindow(html, url);
|
|
let doc = window.document;
|
|
return doc;
|
|
}
|
|
|
|
/**
|
|
* Serializes a document to string.
|
|
*/
|
|
export function serializeDocument(doc: Document): string {
|
|
return (doc as any).serialize();
|
|
}
|
|
|
|
/**
|
|
* DOM Adapter for the server platform based on https://github.com/fgnass/domino.
|
|
*/
|
|
export class DominoAdapter extends BrowserDomAdapter {
|
|
static override makeCurrent() {
|
|
setDomTypes();
|
|
setRootDomAdapter(new DominoAdapter());
|
|
}
|
|
|
|
override readonly supportsDOMEvents = false;
|
|
private static defaultDoc: Document;
|
|
|
|
override createHtmlDocument(): Document {
|
|
return parseDocument('<html><head><title>fakeTitle</title></head><body></body></html>');
|
|
}
|
|
|
|
override getDefaultDocument(): Document {
|
|
if (!DominoAdapter.defaultDoc) {
|
|
DominoAdapter.defaultDoc = domino.createDocument();
|
|
}
|
|
return DominoAdapter.defaultDoc;
|
|
}
|
|
|
|
override isElementNode(node: any): boolean {
|
|
return node ? node.nodeType === DominoAdapter.defaultDoc.ELEMENT_NODE : false;
|
|
}
|
|
override isShadowRoot(node: any): boolean {
|
|
return node.shadowRoot == node;
|
|
}
|
|
|
|
/** @deprecated No longer being used in Ivy code. To be removed in version 14. */
|
|
override getGlobalEventTarget(doc: Document, target: string): EventTarget | null {
|
|
if (target === 'window') {
|
|
return doc.defaultView;
|
|
}
|
|
if (target === 'document') {
|
|
return doc;
|
|
}
|
|
if (target === 'body') {
|
|
return doc.body;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
override getBaseHref(doc: Document): string {
|
|
const length = doc.head.children.length;
|
|
|
|
// The `<base>` can only be a direct child of `<head>` so we can save some
|
|
// execution time by looking through them directly instead of querying for it.
|
|
// See: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/base
|
|
// Note that we can't cache the `href` value itself, because this method gets called with a
|
|
// different document every time which means that in theory the value can be different too.
|
|
for (let i = 0; i < length; i++) {
|
|
const child = doc.head.children[i];
|
|
|
|
// Tag names are always uppercase for HTML nodes.
|
|
if (child.tagName === 'BASE') {
|
|
// TODO(alxhub): Need relative path logic from BrowserDomAdapter here?
|
|
return child.getAttribute('href') || '';
|
|
}
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
override dispatchEvent(el: Node, evt: any) {
|
|
el.dispatchEvent(evt);
|
|
|
|
// Dispatch the event to the window also.
|
|
const doc = el.ownerDocument || el;
|
|
const win = (doc as any).defaultView;
|
|
if (win) {
|
|
win.dispatchEvent(evt);
|
|
}
|
|
}
|
|
|
|
override getUserAgent(): string {
|
|
return 'Fake user agent';
|
|
}
|
|
|
|
override getCookie(name: string): string {
|
|
throw new Error('getCookie has not been implemented');
|
|
}
|
|
}
|