Skip to content

Commit 0dc6380

Browse files
refactor: complete migration of Universal into the repository
Complete the migration of the Universal repository.
2 parents b09ce57 + 3fe432c commit 0dc6380

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+4469
-138
lines changed

.bazelignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ node_modules
44
packages/angular/cli/node_modules
55
packages/angular/create/node_modules
66
packages/angular/pwa/node_modules
7+
packages/angular/ssr/node_modules
78
packages/angular_devkit/architect/node_modules
89
packages/angular_devkit/architect_cli/node_modules
910
packages/angular_devkit/build_angular/node_modules
@@ -12,4 +13,4 @@ packages/angular_devkit/core/node_modules
1213
packages/angular_devkit/schematics/node_modules
1314
packages/angular_devkit/schematics_cli/node_modules
1415
packages/ngtools/webpack/node_modules
15-
packages/schematics/angular/node_modules
16+
packages/schematics/angular/node_modules

.monorepo.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@
2828
"section": "Schematics",
2929
"snapshotRepo": "angular/angular-pwa-builds"
3030
},
31+
"@angular/ssr": {
32+
"name": "Angular SSR",
33+
"links": [
34+
{
35+
"label": "README",
36+
"url": "/packages/angular/ssr/README.md"
37+
}
38+
],
39+
"snapshotRepo": "angular/angular-ssr-builds"
40+
},
3141
"@angular-devkit/architect": {
3242
"name": "Architect",
3343
"links": [

BUILD.bazel

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
# found in the LICENSE file at https://angular.io/license
55
load("@bazel_skylib//rules:common_settings.bzl", "bool_flag")
66
load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin")
7+
load("@npm//@bazel/concatjs:index.bzl", "ts_config")
78

89
package(default_visibility = ["//visibility:public"])
910

@@ -13,6 +14,7 @@ exports_files([
1314
"LICENSE",
1415
"tsconfig.json",
1516
"tsconfig-test.json",
17+
"tsconfig-build-ng.json",
1618
"tsconfig-build.json",
1719
"package.json",
1820
])
@@ -56,3 +58,11 @@ config_setting(
5658
":enable_snapshot_repo_deps": "true",
5759
},
5860
)
61+
62+
ts_config(
63+
name = "tsconfig-build-ng",
64+
src = "tsconfig-build-ng.json",
65+
deps = [
66+
":tsconfig.json",
67+
],
68+
)

CONTRIBUTING.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ The following is the list of supported scopes:
214214
* **@angular/cli**
215215
* **@angular/create**
216216
* **@angular/pwa**
217+
* **@angular/ssr**
217218
* **@angular-devkit/architect**
218219
* **@angular-devkit/architect-cli**
219220
* **@angular-devkit/build-angular**

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ This is a monorepo which contains many tools and packages:
158158

159159
| Project | Package | Version | Links |
160160
|---|---|---|---|
161+
**Angular SSR** | [`@angular/ssr`](https://npmjs.com/package/@angular/ssr) | [![latest](https://img.shields.io/npm/v/%40angular%2Fssr/latest.svg)](https://npmjs.com/package/@angular/ssr) | [![README](https://img.shields.io/badge/README--green.svg)](/packages/angular/ssr/README.md) [![snapshot](https://img.shields.io/badge/snapshot--blue.svg)](https://github.com/angular/angular-ssr-builds)
161162
**Architect** | [`@angular-devkit/architect`](https://npmjs.com/package/@angular-devkit/architect) | [![latest](https://img.shields.io/npm/v/%40angular-devkit%2Farchitect/latest.svg)](https://npmjs.com/package/@angular-devkit/architect) | [![README](https://img.shields.io/badge/README--green.svg)](/packages/angular_devkit/architect/README.md) [![snapshot](https://img.shields.io/badge/snapshot--blue.svg)](https://github.com/angular/angular-devkit-architect-builds)
162163
**Build Angular** | [`@angular-devkit/build-angular`](https://npmjs.com/package/@angular-devkit/build-angular) | [![latest](https://img.shields.io/npm/v/%40angular-devkit%2Fbuild-angular/latest.svg)](https://npmjs.com/package/@angular-devkit/build-angular) | [![README](https://img.shields.io/badge/README--green.svg)](/packages/angular_devkit/build_angular/README.md) [![snapshot](https://img.shields.io/badge/snapshot--blue.svg)](https://github.com/angular/angular-devkit-build-angular-builds)
163164
**Build Webpack** | [`@angular-devkit/build-webpack`](https://npmjs.com/package/@angular-devkit/build-webpack) | [![latest](https://img.shields.io/npm/v/%40angular-devkit%2Fbuild-webpack/latest.svg)](https://npmjs.com/package/@angular-devkit/build-webpack) | [![README](https://img.shields.io/badge/README--green.svg)](/packages/angular_devkit/build_webpack/README.md) [![snapshot](https://img.shields.io/badge/snapshot--blue.svg)](https://github.com/angular/angular-devkit-build-webpack-builds)

WORKSPACE

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ yarn_install(
6969
data = [
7070
"//:.yarn/releases/yarn-1.22.17.cjs",
7171
"//:.yarnrc",
72+
"//:tools/postinstall/patches/@angular+bazel+16.0.0-next.6.patch",
73+
"//:tools/postinstall/patches/@bazel+concatjs+5.8.1.patch",
7274
],
7375
# Currently disabled due to:
7476
# 1. Missing Windows support currently.

constants.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ RELEASE_ENGINES_YARN = ">= 1.13.0"
66
SNAPSHOT_REPOS = {
77
"@angular/cli": "angular/cli-builds",
88
"@angular/pwa": "angular/angular-pwa-builds",
9+
"@angular/ssr": "angular/angular-ssr-builds",
910
"@angular-devkit/architect": "angular/angular-devkit-architect-builds",
1011
"@angular-devkit/architect-cli": "angular/angular-devkit-architect-cli-builds",
1112
"@angular-devkit/build-angular": "angular/angular-devkit-build-angular-builds",
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
# Important Considerations when Using Angular Universal
2+
3+
## Introduction
4+
5+
Although the goal of the Universal project is the ability to seamlessly render an Angular
6+
application on the server, there are some inconsistencies that you should consider. First,
7+
there is the obvious discrepancy between the server and browser environments. When rendering
8+
on the server, your application is in an ephemeral or "snapshot" state. The application is
9+
fully rendered once, with the resulting HTML returned, and the remaining application state
10+
destroyed until the next render. Next, the server environment inherently does not have the
11+
same capabilities as the browser (and has some that likewise the browser does not). For
12+
instance, the server does not have any concept of cookies. You can polyfill this and other
13+
functionality, but there is no perfect solution for this. In later sections, we'll walk
14+
through potential mitigations to reduce the scope of errors when rendering on the server.
15+
16+
Please also note the goal of SSR: improved initial render time for your application. This
17+
means that anything that has the potential to reduce the speed of your application in this
18+
initial render should be avoided or sufficiently guarded against. Again, we'll review how
19+
to accomplish this in later sections.
20+
21+
## "window is not defined"
22+
23+
One of the most common issues when using Angular Universal is the lack of browser global
24+
variables in the server environment. This is because the Universal project uses
25+
[domino](https://github.com/fgnass/domino) as the server DOM rendering engine. As a result,
26+
there is certain functionality that won't be present or supported on the server. This
27+
includes the `window` and `document` global objects, cookies, certain HTML elements (like canvas),
28+
and several others. There is no exhaustive list, so please be aware of the fact that if you
29+
see an error like this, where a previously-accessible global is not defined, it's likely because
30+
that global is not available through domino.
31+
32+
> Fun fact: Domino stands for "DOM in Node"
33+
34+
### How to fix?
35+
36+
#### Strategy 1: Injection
37+
38+
Frequently, the needed global is available through the Angular platform via Dependency Injection (DI).
39+
For instance, the global `document` is available through the `DOCUMENT` token. Additionally, a _very_
40+
primitive version of both `window` and `location` exist through the `DOCUMENT` object. For example:
41+
42+
```ts
43+
// example.service.ts
44+
import { Injectable, Inject } from '@angular/core';
45+
import { DOCUMENT } from '@angular/common';
46+
47+
@Injectable()
48+
export class ExampleService {
49+
constructor(@Inject(DOCUMENT) private _doc: Document) {}
50+
51+
getWindow(): Window | null {
52+
return this._doc.defaultView;
53+
}
54+
55+
getLocation(): Location {
56+
return this._doc.location;
57+
}
58+
59+
createElement(tag: string): HTMLElement {
60+
return this._doc.createElement(tag);
61+
}
62+
}
63+
```
64+
65+
Please be judicious about using these references, and lower your expectations about their capabilities. `localStorage`
66+
is one frequently-requested API that won't work how you want it to out of the box. If you need to write your own library
67+
components, please consider using this method to provide similar functionality on the server (this is what Angular CDK
68+
and Material do).
69+
70+
#### Strategy 2: Guards
71+
72+
If you can't inject the proper global value you need from the Angular platform, you can "guard" against
73+
invocation of browser code, so long as you don't need to access that code on the server. For instance,
74+
often invocations of the global `window` element are to get window size, or some other visual aspect.
75+
However, on the server, there is no concept of "screen", and so this functionality is rarely needed.
76+
77+
You may read online and elsewhere that the recommended approach is to use `isPlatformBrowser` or
78+
`isPlatformServer`. This guidance is **incorrect**. This is because you wind up creating platform-specific
79+
code branches in your application code. This not only increases the size of your application unnecessarily,
80+
but it also adds complexity that then has to be maintained. By separating code into separate platform-specific
81+
modules and implementations, your base code can remain about business logic, and platform-specific exceptions
82+
are handled as they should be: on a case-by-case abstraction basis. This can be accomplished using Angular's Dependency
83+
Injection (DI) in order to remove the offending code and drop in a replacement at runtime. Here's an example:
84+
85+
```ts
86+
// window-service.ts
87+
import { Injectable } from '@angular/core';
88+
89+
@Injectable()
90+
export class WindowService {
91+
getWidth(): number {
92+
return window.innerWidth;
93+
}
94+
}
95+
```
96+
97+
```ts
98+
// server-window.service.ts
99+
import { Injectable } from '@angular/core';
100+
import { WindowService } from './window.service';
101+
102+
@Injectable()
103+
export class ServerWindowService extends WindowService {
104+
getWidth(): number {
105+
return 0;
106+
}
107+
}
108+
```
109+
110+
```ts
111+
// app-server.module.ts
112+
import {NgModule} from '@angular/core';
113+
import {WindowService} from './window.service';
114+
import {ServerWindowService} from './server-window.service';
115+
116+
@NgModule({
117+
providers: [{
118+
provide: WindowService,
119+
useClass: ServerWindowService,
120+
}]
121+
})
122+
```
123+
124+
If you have a component provided by a third-party that is not Universal-compatible out of the box,
125+
you can create two separate modules for browser and server (the server module you should already have),
126+
in addition to your base app module. The base app module will contain all of your platform-agnostic code,
127+
the browser module will contain all of your browser-specific/server-incompatible code, and vice-versa for
128+
your server module. In order to avoid editing too much template code, you can create a no-op component
129+
to drop in for the library component. Here's an example:
130+
131+
```ts
132+
// example.component.ts
133+
import { Component } from '@angular/core';
134+
135+
@Component({
136+
selector: 'example-component',
137+
template: `<library-component></library-component>`, // this is provided by a third-party lib
138+
// that causes issues rendering on Universal
139+
})
140+
export class ExampleComponent {}
141+
```
142+
143+
```ts
144+
// app.module.ts
145+
import {NgModule} from '@angular/core';
146+
import {ExampleComponent} from './example.component';
147+
148+
@NgModule({
149+
declarations: [ExampleComponent],
150+
})
151+
```
152+
153+
```ts
154+
// browser-app.module.ts
155+
import {NgModule} from '@angular/core';
156+
import {LibraryModule} from 'some-lib';
157+
import {AppModule} from './app.module';
158+
159+
@NgModule({
160+
imports: [AppModule, LibraryModule],
161+
})
162+
```
163+
164+
```ts
165+
// library-shim.component.ts
166+
import { Component } from '@angular/core';
167+
168+
@Component({
169+
selector: 'library-component',
170+
template: '',
171+
})
172+
export class LibraryShimComponent {}
173+
```
174+
175+
```ts
176+
// server.app.module.ts
177+
import { NgModule } from '@angular/core';
178+
import { LibraryShimComponent } from './library-shim.component';
179+
import { AppModule } from './app.module';
180+
181+
@NgModule({
182+
imports: [AppModule],
183+
declarations: [LibraryShimComponent],
184+
})
185+
export class ServerAppModule {}
186+
```
187+
188+
#### Strategy 3: Shims
189+
190+
If all else fails, and you simply must have access to some sort of browser functionality, you can patch
191+
the global scope of the server environment to include the globals you need. For instance:
192+
193+
```ts
194+
// server.ts
195+
global['window'] = {
196+
// properties you need implemented here...
197+
};
198+
```
199+
200+
This can be applied to any undefined element. Please be careful when you do this, as playing with the global
201+
scope is generally considered an anti-pattern.
202+
203+
> Fun fact: a shim is a patch for functionality that will never be supported on a given platform. A
204+
> polyfill is a patch for functionality that is planned to be supported, or is supported on newer versions
205+
206+
## Application is slow, or worse, won't render
207+
208+
The Angular Universal rendering process is straightforward, but just as simply can be blocked or slowed down
209+
by well-meaning or innocent-looking code. First, some background on the rendering process. When a render
210+
request is made for platform-server (the Angular Universal platform), a single route navigation is executed.
211+
When that navigation completes, meaning that all Zone.js macrotasks are completed, the DOM in whatever state
212+
it's in at that time is returned to the user.
213+
214+
> A Zone.js macrotask is just a JavaScript macrotask that executes in/is patched by Zone.js
215+
216+
This means that if there is a process, like a microtask, that takes up ticks to complete, or a long-standing
217+
HTTP request, the rendering process will not complete, or will take longer. Macrotasks include calls to globals
218+
like `setTimeout` and `setInterval`, and `Observables`. Calling these without cancelling them, or letting them run
219+
longer than needed on the server could result in suboptimal rendering.
220+
221+
> It may be worth brushing up on the JavaScript event loop and learning the difference between microtasks
222+
> and macrotasks, if you don't know it already. [Here's](https://javascript.info/event-loop) a good reference.
223+
224+
## My HTTP, Firebase, WebSocket, etc. won't finish before render!
225+
226+
Similarly to the above section on waiting for macrotasks to complete, the flip-side is that the platform will
227+
not wait for microtasks to complete before finishing the render. In Angular Universal, we have patched the
228+
Angular HTTP client to turn it into a macrotask, to ensure that any needed HTTP requests complete for a given
229+
render. However, this type of patch may not be appropriate for all microtasks, and so it is recommended you use
230+
your best judgment on how to proceed. You can look at the code reference for how Universal wraps a task to turn
231+
it into a macrotask, or you can simply opt to change the server behavior of the given tasks.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
## API Report File for "@angular/ssr"
2+
3+
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
4+
5+
```ts
6+
7+
import { ApplicationRef } from '@angular/core';
8+
import { StaticProvider } from '@angular/core';
9+
import { Type } from '@angular/core';
10+
11+
// @public
12+
export class CommonEngine {
13+
constructor(bootstrap?: Type<{}> | (() => Promise<ApplicationRef>) | undefined, providers?: StaticProvider[]);
14+
render(opts: CommonEngineRenderOptions): Promise<string>;
15+
}
16+
17+
// @public (undocumented)
18+
export interface CommonEngineRenderOptions {
19+
// (undocumented)
20+
bootstrap?: Type<{}> | (() => Promise<ApplicationRef>);
21+
// (undocumented)
22+
document?: string;
23+
// (undocumented)
24+
documentFilePath?: string;
25+
inlineCriticalCss?: boolean;
26+
// (undocumented)
27+
providers?: StaticProvider[];
28+
publicPath?: string;
29+
// (undocumented)
30+
url?: string;
31+
}
32+
33+
// @public
34+
export function ngExpressEngine(setupOptions: Readonly<NgExpressEngineOptions>): (path: string, options: NgExpressEngineRenderOptions, callback: (err?: Error | null, html?: string) => void) => void;
35+
36+
// @public
37+
export interface NgExpressEngineOptions extends Pick<CommonEngineRenderOptions, 'providers' | 'publicPath' | 'inlineCriticalCss'> {
38+
// (undocumented)
39+
bootstrap: NonNullable<CommonEngineRenderOptions['bootstrap']>;
40+
}
41+
42+
// @public (undocumented)
43+
export type NgExpressEngineRenderOptions = CommonEngineRenderOptions;
44+
45+
// (No @packageDocumentation comment for this package)
46+
47+
```

0 commit comments

Comments
 (0)