Skip to content
This repository was archived by the owner on Jan 6, 2025. It is now read-only.

Commit 5874498

Browse files
authored
fix(server): disable breakpoints correctly and avoid style overuse (#1378)
1 parent 51b6ebf commit 5874498

File tree

11 files changed

+916
-83
lines changed

11 files changed

+916
-83
lines changed

angular.json

+12-1
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@
185185
"builder": "@angular-devkit/build-angular:server",
186186
"options": {
187187
"outputPath": "dist/universal-demo-app/server",
188-
"main": "projects/apps/universal-demo-app/src/main.server.ts",
188+
"main": "projects/apps/universal-demo-app/server.ts",
189189
"tsConfig": "projects/apps/universal-demo-app/tsconfig.server.json",
190190
"preserveSymlinks": true,
191191
"sourceMap": true,
@@ -203,6 +203,17 @@
203203
}
204204
}
205205
},
206+
"serve-ssr": {
207+
"builder": "@nguniversal/builders:ssr-dev-server",
208+
"defaultConfiguration": "production",
209+
"options": {},
210+
"configurations": {
211+
"production": {
212+
"browserTarget": "universal-demo-app:build:production",
213+
"serverTarget": "universal-demo-app:server:production"
214+
}
215+
}
216+
},
206217
"serve": {
207218
"builder": "@angular-devkit/build-angular:dev-server",
208219
"configurations": {

package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"demo:serve": "ng serve demo-app",
2222
"stamp": "ts-node tools/package-tools/version-placeholders.ts --module=commonjs",
2323
"build:universal-demo-app": "ng run universal-demo-app:build:production && ng run universal-demo-app:server",
24+
"serve:universal-demo-app": "ng run universal-demo-app:serve-ssr",
2425
"test": "ng test @angular/flex-layout",
2526
"test:ssr": "webpack --config test/webpack-spec-ssr-bundle.js && jasmine --config=test/jasmine-ssr.json",
2627
"lint": "stylelint projects/libs/**/*.scss --config=stylelint-config.json && tslint --project ./tsconfig.json --config tslint.json",
@@ -40,8 +41,8 @@
4041
"@angular/platform-browser-dynamic": "~13.0.0",
4142
"@angular/platform-server": "~13.0.0",
4243
"@angular/router": "~13.0.0",
44+
"@nguniversal/builders": "^13.0.1",
4345
"@nguniversal/express-engine": "^13.0.1",
44-
"@types/express": "^4.17.13",
4546
"express": "^4.17.1",
4647
"rxjs": "~7.4.0",
4748
"tslib": "^2.3.0",
@@ -53,6 +54,8 @@
5354
"@angular/cli": "~13.0.0",
5455
"@angular/compiler-cli": "~13.0.0",
5556
"@ngtools/webpack": "^13.0.3",
57+
"@nguniversal/builders": "^13.0.1",
58+
"@types/express": "^4.17.0",
5659
"@types/jasmine": "~3.10.0",
5760
"@types/minimatch": "^3.0.5",
5861
"@types/node": "^12.11.1",
+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import 'zone.js/dist/zone-node';
2+
3+
import { ngExpressEngine } from '@nguniversal/express-engine';
4+
import * as express from 'express';
5+
import { join } from 'path';
6+
7+
import { AppServerModule } from './src/app/app.server.module';
8+
import { APP_BASE_HREF } from '@angular/common';
9+
import { existsSync } from 'fs';
10+
11+
// The Express app is exported so that it can be used by serverless Functions.
12+
export function app(): express.Express {
13+
const server = express();
14+
const distFolder = join(process.cwd(), 'dist/universal-demo-app/browser');
15+
const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';
16+
17+
server.engine('html', ngExpressEngine({
18+
bootstrap: AppServerModule,
19+
}));
20+
21+
server.set('view engine', 'html');
22+
server.set('views', distFolder);
23+
24+
// Example Express Rest API endpoints
25+
// server.get('/api/**', (req, res) => { });
26+
// Serve static files from /browser
27+
server.get('*.*', express.static(distFolder, {
28+
maxAge: '1y'
29+
}));
30+
31+
// All regular routes use the Universal engine
32+
server.get('*', (req, res) => {
33+
res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
34+
});
35+
36+
return server;
37+
}
38+
39+
function run(): void {
40+
const port = process.env['PORT'] || 4000;
41+
42+
// Start up the Node server
43+
const server = app();
44+
server.listen(port, () => {
45+
console.log(`Node Express server listening on http://localhost:${port}`);
46+
});
47+
}
48+
49+
// Webpack will replace 'require' with '__webpack_require__'
50+
// '__non_webpack_require__' is a proxy to Node 'require'
51+
// The below code is to ensure that the server is run only when not requiring the bundle.
52+
declare const __non_webpack_require__: NodeRequire;
53+
const mainModule = __non_webpack_require__.main;
54+
const moduleFilename = mainModule && mainModule.filename || '';
55+
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
56+
run();
57+
}
58+
59+
export * from './src/main.server';

projects/apps/universal-demo-app/tsconfig.server.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"entryModule": "app/app.server.module#AppServerModule"
1212
},
1313
"files": [
14-
"src/main.server.ts"
14+
"src/main.server.ts",
15+
"server.ts"
1516
]
1617
}

projects/libs/flex-layout/core/breakpoints/break-point-registry.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -30,19 +30,19 @@ export class BreakPointRegistry {
3030
* Search breakpoints by alias (e.g. gt-xs)
3131
*/
3232
findByAlias(alias: string): OptionalBreakPoint {
33-
return !alias ? null : this.findWithPredicate(alias, (bp) => bp.alias == alias);
33+
return !alias ? null : this.findWithPredicate(alias, (bp) => bp.alias === alias);
3434
}
3535

3636
findByQuery(query: string): OptionalBreakPoint {
37-
return this.findWithPredicate(query, (bp) => bp.mediaQuery == query);
37+
return this.findWithPredicate(query, (bp) => bp.mediaQuery === query);
3838
}
3939

4040
/**
4141
* Get all the breakpoints whose ranges could overlapping `normal` ranges;
4242
* e.g. gt-sm overlaps md, lg, and xl
4343
*/
4444
get overlappings(): BreakPoint[] {
45-
return this.items.filter(it => it.overlapping == true);
45+
return this.items.filter(it => it.overlapping);
4646
}
4747

4848
/**
@@ -58,7 +58,7 @@ export class BreakPointRegistry {
5858
* for property layoutGtSM.
5959
*/
6060
get suffixes(): string[] {
61-
return this.items.map(it => !!it.suffix ? it.suffix : '');
61+
return this.items.map(it => it?.suffix ?? '');
6262
}
6363

6464
/**
@@ -68,10 +68,10 @@ export class BreakPointRegistry {
6868
searchFn: (bp: BreakPoint) => boolean): OptionalBreakPoint {
6969
let response = this.findByMap.get(key);
7070
if (!response) {
71-
response = this.items.find(searchFn) || null;
71+
response = this.items.find(searchFn) ?? null;
7272
this.findByMap.set(key, response);
7373
}
74-
return response || null;
74+
return response ?? null;
7575

7676
}
7777

projects/libs/flex-layout/core/match-media/match-media.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export class MatchMedia implements OnDestroy {
4949
*/
5050
isActive(mediaQuery: string): boolean {
5151
const mql = this.registry.get(mediaQuery);
52-
return !!mql ? mql.matches : this.registerQuery(mediaQuery).some(m => m.matches);
52+
return mql?.matches ?? this.registerQuery(mediaQuery).some(m => m.matches);
5353
}
5454

5555
/**

projects/libs/flex-layout/core/media-marshaller/media-marshaller.ts

+24-12
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export interface ElementMatcher {
4343
*/
4444
@Injectable({providedIn: 'root'})
4545
export class MediaMarshaller {
46+
private _useFallbacks = true;
4647
private activatedBreakpoints: BreakPoint[] = [];
4748
private elementMap: ElementMap = new Map();
4849
private elementKeyMap: ElementKeyMap = new WeakMap();
@@ -53,7 +54,11 @@ export class MediaMarshaller {
5354
private subject: Subject<ElementMatcher> = new Subject();
5455

5556
get activatedAlias(): string {
56-
return this.activatedBreakpoints[0] ? this.activatedBreakpoints[0].alias : '';
57+
return this.activatedBreakpoints[0]?.alias ?? '';
58+
}
59+
60+
set useFallbacks(value: boolean) {
61+
this._useFallbacks = value;
5762
}
5863

5964
constructor(protected matchMedia: MatchMedia,
@@ -68,18 +73,20 @@ export class MediaMarshaller {
6873
*/
6974
onMediaChange(mc: MediaChange) {
7075
const bp: BreakPoint | null = this.findByQuery(mc.mediaQuery);
76+
7177
if (bp) {
7278
mc = mergeAlias(mc, bp);
7379

74-
if (mc.matches && this.activatedBreakpoints.indexOf(bp) === -1) {
80+
const bpIndex = this.activatedBreakpoints.indexOf(bp);
81+
82+
if (mc.matches && bpIndex === -1) {
7583
this.activatedBreakpoints.push(bp);
7684
this.activatedBreakpoints.sort(sortDescendingPriority);
7785

7886
this.updateStyles();
79-
80-
} else if (!mc.matches && this.activatedBreakpoints.indexOf(bp) !== -1) {
87+
} else if (!mc.matches && bpIndex !== -1) {
8188
// Remove the breakpoint when it's deactivated
82-
this.activatedBreakpoints.splice(this.activatedBreakpoints.indexOf(bp), 1);
89+
this.activatedBreakpoints.splice(bpIndex, 1);
8390
this.activatedBreakpoints.sort(sortDescendingPriority);
8491

8592
this.updateStyles();
@@ -193,7 +200,6 @@ export class MediaMarshaller {
193200
this.clearElement(el, k);
194201
}
195202
});
196-
197203
});
198204
}
199205

@@ -204,6 +210,7 @@ export class MediaMarshaller {
204210
*/
205211
clearElement(element: HTMLElement, key: string): void {
206212
const builders = this.clearMap.get(element);
213+
207214
if (builders) {
208215
const clearFn: ClearCallback = builders.get(key) as ClearCallback;
209216
if (!!clearFn) {
@@ -316,12 +323,20 @@ export class MediaMarshaller {
316323
for (let i = 0; i < this.activatedBreakpoints.length; i++) {
317324
const activatedBp = this.activatedBreakpoints[i];
318325
const valueMap = bpMap.get(activatedBp.alias);
326+
319327
if (valueMap) {
320328
if (key === undefined || (valueMap.has(key) && valueMap.get(key) != null)) {
321329
return valueMap;
322330
}
323331
}
324332
}
333+
334+
// On the server, we explicitly have an "all" section filled in to begin with.
335+
// So we don't need to aggressively find a fallback if no explicit value exists.
336+
if (!this._useFallbacks) {
337+
return undefined;
338+
}
339+
325340
const lastHope = bpMap.get('');
326341
return (key === undefined || lastHope && lastHope.has(key)) ? lastHope : undefined;
327342
}
@@ -347,14 +362,11 @@ export class MediaMarshaller {
347362
function initBuilderMap(map: BuilderMap,
348363
element: HTMLElement,
349364
key: string,
350-
input?: UpdateCallback | ClearCallback): void {
365+
input?: Builder): void {
351366
if (input !== undefined) {
352-
let oldMap = map.get(element);
353-
if (!oldMap) {
354-
oldMap = new Map();
355-
map.set(element, oldMap);
356-
}
367+
const oldMap = map.get(element) ?? new Map();
357368
oldMap.set(key, input);
369+
map.set(element, oldMap);
358370
}
359371
}
360372

projects/libs/flex-layout/extended/style/style.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export class StyleDirective extends BaseDirective2 implements DoCheck {
6262
this.ngStyleInstance = new NgStyle(elementRef, differs, renderer2);
6363
}
6464
this.init();
65-
const styles = this.nativeElement.getAttribute('style') || '';
65+
const styles = this.nativeElement.getAttribute('style') ?? '';
6666
this.fallbackStyles = this.buildStyleMap(styles);
6767
this.isServer = serverLoaded && isPlatformServer(platformId);
6868
}
@@ -92,7 +92,7 @@ export class StyleDirective extends BaseDirective2 implements DoCheck {
9292
protected buildStyleMap(styles: NgStyleType): NgStyleMap {
9393
// Always safe-guard (aka sanitize) style property values
9494
const sanitizer: NgStyleSanitizer = (val: any) =>
95-
this.sanitizer.sanitize(SecurityContext.STYLE, val) || '';
95+
this.sanitizer.sanitize(SecurityContext.STYLE, val) ?? '';
9696
if (styles) {
9797
switch (getType(styles)) {
9898
case 'string': return buildMapFromList(buildRawList(styles),

projects/libs/flex-layout/server/server-match-media.ts

+3-17
Original file line numberDiff line numberDiff line change
@@ -78,27 +78,13 @@ export class ServerMediaQueryList implements MediaQueryList {
7878
}
7979

8080
/** Don't need to remove listeners in the server environment */
81-
removeListener(_: MediaQueryListListener | null) {
81+
removeListener() {
8282
}
8383

84-
addEventListener<K extends keyof
85-
MediaQueryListEventMap>(_: K,
86-
__: (this: MediaQueryList,
87-
ev: MediaQueryListEventMap[K]) => any,
88-
___?: boolean | AddEventListenerOptions): void;
89-
addEventListener(_: string,
90-
__: EventListenerOrEventListenerObject,
91-
___?: boolean | AddEventListenerOptions) {
84+
addEventListener() {
9285
}
9386

94-
removeEventListener<K extends keyof
95-
MediaQueryListEventMap>(_: K,
96-
__: (this: MediaQueryList,
97-
ev: MediaQueryListEventMap[K]) => any,
98-
___?: boolean | EventListenerOptions): void;
99-
removeEventListener(_: string,
100-
__: EventListenerOrEventListenerObject,
101-
___?: boolean | EventListenerOptions) {
87+
removeEventListener() {
10288
}
10389

10490
dispatchEvent(_: Event): boolean {

0 commit comments

Comments
 (0)