Skip to content

Commit

Permalink
Add global styles properly in the SSR context
Browse files Browse the repository at this point in the history
Fixes #18 #48 #96 #407
  • Loading branch information
devoto13 committed May 15, 2024
1 parent 551ccb2 commit 5d26d6d
Show file tree
Hide file tree
Showing 17 changed files with 357 additions and 11 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ jobs:
- run: yarn test:demo
- run: yarn add -D chromedriver@~`google-chrome --version | awk '{print $3}' | awk -F. '{print $1}'`
- run: yarn test:integration
- run: yarn test:integration:ssr
check6:
name: Font Awesome 6
runs-on: ubuntu-latest
Expand All @@ -48,3 +49,4 @@ jobs:
- run: yarn test:demo
- run: yarn add -D chromedriver@~`google-chrome --version | awk '{print $3}' | awk -F. '{print $1}'`
- run: yarn test:integration
- run: yarn test:integration:ssr
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,4 @@ Thumbs.db
!/.yarn/plugins
!/.yarn/sdks
!/.yarn/versions
!/.yarn/patches
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
diff --git a/src/builders/protractor/index.js b/src/builders/protractor/index.js
index 34d8f76bac7ece1fcb6d7afd722ec99fad4efccc..6ec016ee9d1a04ba0b1dc4742340a2cbe2b9b80f 100755
--- a/src/builders/protractor/index.js
+++ b/src/builders/protractor/index.js
@@ -108,17 +108,7 @@ async function execute(options, context) {
const serverOptions = await context.getTargetOptions(target);
const overrides = {
watch: false,
- liveReload: false,
};
- if (options.host !== undefined) {
- overrides.host = options.host;
- }
- else if (typeof serverOptions.host === 'string') {
- options.host = serverOptions.host;
- }
- else {
- options.host = overrides.host = 'localhost';
- }
if (options.port !== undefined) {
overrides.port = options.port;
}
5 changes: 3 additions & 2 deletions angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,9 @@
"devServerTarget": "demo:serve:production",
"webdriverUpdate": false
},
"development": {
"devServerTarget": "demo:serve:development"
"ssr": {
"devServerTarget": "demo:serve-ssr:production",
"webdriverUpdate": false
}
},
"defaultConfiguration": "production"
Expand Down
42 changes: 42 additions & 0 deletions docs/guide/adding-css.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Adding CSS

For Font Awesome icon to render properly, it needs global Font Awesome styles to be added to the page. By default, the library will automatically add the necessary styles to the page before rendering an icon.

If you have issues with this approach, you can disable it by setting `FaConfig.autoAddCss` to `false`:

```typescript
import { FaConfig } from '@fortawesome/angular-fontawesome';

export class AppComponent {
constructor(faConfig: FaConfig) {
faConfig.autoAddCss = false;
}
}
```

And instead add the styles manually to your application. You can find the necessary styles in the `node_modules/@fortawesome/fontawesome-svg-core/styles.css` file. Then add them to the application global styles in the `angular.json` file:

```json
{
"projects": {
"your-project-name": {
"architect": {
"build": {
"options": {
"styles": [
"node_modules/@fortawesome/fontawesome-svg-core/styles.css",
"src/styles.css"
]
}
}
}
}
}
}
```

One common case when this is necessary is when using Shadow DOM. Angular includes [certain non-trivial logic](https://angular.io/guide/view-encapsulation#mixing-encapsulation-modes) to ensure that global styles work as expected inside the shadow root which can't be applied when styles are added automatically.

## Size concerns

If you are concerned about the size of the Font Awesome global styles, you may extract only the necessary styles from the `node_modules/@fortawesome/fontawesome-svg-core/styles.css` file and add them instead. This way, you can reduce the size of the global styles to only what is necessary for your application. But be aware that this is not officially supported and may break with future updates to Font Awesome. Make sure to revisit the manually extracted styles every time the library is updated or a new Font Awesome feature is used.
16 changes: 16 additions & 0 deletions docs/upgrading/0.14.0-0.15.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,19 @@ Dynamic animation can be achieved by binding the `animation` input to `undefined
## Remove usage of the `styles` and `classes` inputs

Previously deprecated `styles` and `classes` inputs in all components were removed. These inputs don't work the way one would expect and cause a lot of confusion. For the majority of the cases, one should use regular [class and style bindings](https://angular.io/guide/class-binding) provided by Angular. For those rare cases, when it is not enough, there is a guide on how one can style component's internal elements at their own risk - [Styling icon internals](https://github.com/FortAwesome/angular-fontawesome/blob/master/docs/guide/styling-icon-internals.md).

## Styles are correctly added in the SSR context

Previously, the library didn't correctly add global styles in the SSR context. If you have added global styles to your application to work around issues like [#407](https://github.com/FortAwesome/angular-fontawesome/issues/407), [#18](https://github.com/FortAwesome/angular-fontawesome/issues/18) or [#48](https://github.com/FortAwesome/angular-fontawesome/issues/48), you can either remove the workaround or alternatively, disable automatic styles injection by setting `FaConfig.autoAddCss` to `false`:

```typescript
import { FaConfig } from '@fortawesome/angular-fontawesome';

export class AppComponent {
constructor(faConfig: FaConfig) {
faConfig.autoAddCss = false;
}
}
```

Not doing this should not cause any issues, but it will lead to same styles being added twice to the page.
1 change: 1 addition & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,4 @@ Guides on specific topics or use cases.
* [Storybook](./guide/storybook.md)
* [Advanced uses](./guide/advanced-uses.md)
* [Styling icon internals](./guide/styling-icon-internals.md)
* [Adding CSS](./guide/adding-css.md)
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"test:schematics": "ts-node --project projects/schematics/tsconfig.json node_modules/.bin/jasmine projects/schematics/src/**/*.spec.ts",
"test:demo": "ng test demo --watch=false --browsers=ChromeCI",
"test:integration": "ng e2e demo",
"test:integration:ssr": "ng e2e demo --configuration ssr",
"lint": "ng lint",
"start": "ng serve demo",
"start:ssr": "ng run demo:serve-ssr",
Expand All @@ -26,7 +27,7 @@
},
"homepage": "https://github.com/FortAwesome/angular-fontawesome",
"devDependencies": {
"@angular-devkit/build-angular": "^17.3.7",
"@angular-devkit/build-angular": "patch:@angular-devkit/build-angular@npm%3A17.3.7#~/.yarn/patches/@angular-devkit-build-angular-npm-17.3.7-60e65bd832.patch",
"@angular-devkit/core": "^17.3.7",
"@angular-devkit/schematics": "^17.3.7",
"@angular-eslint/builder": "^17.0.0",
Expand Down
28 changes: 27 additions & 1 deletion projects/demo/e2e/src/app.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,43 @@
import { browser, logging } from 'protractor';
import { browser, ElementFinder, logging } from 'protractor';
import { appPage } from './app.page';

describe('Angular FontAwesome demo', () => {
beforeEach(async () => {
// TODO: Migrate off Protractor as wait for Angular does not seem to work in the standalone mode
browser.waitForAngularEnabled(false);
await appPage.navigateTo();
await browser.sleep(1000);
});

it('should render all icons', async () => {
expect(await appPage.icons.count()).toBe(46);
});

it('should only add styles once', async () => {
const styles: string[] = await appPage.styles.map((style: ElementFinder) => style.getAttribute('innerHTML'));
const fontAwesomeStyles = styles.filter((style) => style.includes('.svg-inline--fa'));

expect(fontAwesomeStyles.length).toBe(1);
});

it('should include styles in the server-side-rendered page', async () => {
const context = await appPage.appRoot.getAttribute('ng-server-context');
if (context !== 'ssr') {
// Skip the test if the page is not server-side rendered.
return;
}

const render1 = await fetch(browser.baseUrl);
const text1 = await render1.text();
expect(text1).toContain('.svg-inline--fa');

// Repeated second time to make sure that second render also includes the styles.
// To achieve it we use WeakSet instead of a simple global variable.
const render2 = await fetch(browser.baseUrl);
const text2 = await render2.text();
expect(text2).toContain('.svg-inline--fa');
});

afterEach(async () => {
// Assert that there are no errors emitted from the browser
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
Expand Down
5 changes: 4 additions & 1 deletion projects/demo/e2e/src/app.page.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { $$, browser } from 'protractor';
import { $, $$, browser } from 'protractor';

export class AppPage {
readonly icons = $$('svg');
readonly styles = $$('style');

readonly appRoot = $('app-root');

async navigateTo() {
await browser.get(browser.baseUrl);
Expand Down
22 changes: 22 additions & 0 deletions src/lib/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Injectable } from '@angular/core';
import { config } from '@fortawesome/fontawesome-svg-core';
import { IconDefinition, IconPrefix } from './types';

@Injectable({ providedIn: 'root' })
Expand Down Expand Up @@ -26,4 +27,25 @@ export class FaConfig {
* @default false
*/
fixedWidth?: boolean;

/**
* Automatically add Font Awesome styles to the document when icon is rendered.
*
* For the majority of the cases the automatically added CSS is sufficient,
* please refer to the linked guide for more information on when to disable
* this feature.
*
* @see {@link: https://github.com/FortAwesome/angular-fontawesome/blob/main/docs/guide/adding-css.md}
* @default true
*/
set autoAddCss(value: boolean) {
config.autoAddCss = value;
this._autoAddCss = value;
}

get autoAddCss() {
return this._autoAddCss;
}

private _autoAddCss = true;
}
7 changes: 6 additions & 1 deletion src/lib/icon/icon.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Component, HostBinding, Input, OnChanges, Optional, SimpleChanges } from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { Component, HostBinding, inject, Input, OnChanges, Optional, SimpleChanges } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import {
FaSymbol,
Expand All @@ -18,6 +19,7 @@ import { faWarnIfIconDefinitionMissing } from '../shared/errors/warn-if-icon-htm
import { faWarnIfIconSpecMissing } from '../shared/errors/warn-if-icon-spec-missing';
import { AnimationProp, FaProps } from '../shared/models/props.model';
import { faClassList } from '../shared/utils/classlist.util';
import { ensureCss } from '../shared/utils/css';
import { faNormalizeIconSpec } from '../shared/utils/normalize-icon-spec.util';
import { FaStackItemSizeDirective } from '../stack/stack-item-size.directive';
import { FaStackComponent } from '../stack/stack.component';
Expand Down Expand Up @@ -71,6 +73,8 @@ export class FaIconComponent implements OnChanges {

@HostBinding('innerHTML') renderedIconHTML: SafeHtml;

private document = inject(DOCUMENT);

constructor(
private sanitizer: DomSanitizer,
private config: FaConfig,
Expand All @@ -96,6 +100,7 @@ export class FaIconComponent implements OnChanges {
const iconDefinition = this.findIconDefinition(this.icon ?? this.config.fallbackIcon);
if (iconDefinition != null) {
const params = this.buildParams();
ensureCss(this.document, this.config);
const renderedIcon = icon(iconDefinition, params);
this.renderedIconHTML = this.sanitizer.bypassSecurityTrustHtml(renderedIcon.html.join('\n'));
}
Expand Down
9 changes: 8 additions & 1 deletion src/lib/layers/layers-counter.component.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { Component, HostBinding, Input, OnChanges, Optional, SimpleChanges } from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { Component, HostBinding, inject, Input, OnChanges, Optional, SimpleChanges } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { counter, CounterParams } from '@fortawesome/fontawesome-svg-core';
import { FaConfig } from '../config';
import { faWarnIfParentNotExist } from '../shared/errors/warn-if-parent-not-exist';
import { ensureCss } from '../shared/utils/css';
import { FaLayersComponent } from './layers.component';

@Component({
Expand All @@ -19,6 +22,9 @@ export class FaLayersCounterComponent implements OnChanges {

@HostBinding('innerHTML') renderedHTML: SafeHtml;

private document = inject(DOCUMENT);
private config = inject(FaConfig);

constructor(
@Optional() private parent: FaLayersComponent,
private sanitizer: DomSanitizer,
Expand All @@ -41,6 +47,7 @@ export class FaLayersCounterComponent implements OnChanges {
}

private updateContent(params: CounterParams) {
ensureCss(this.document, this.config);
this.renderedHTML = this.sanitizer.bypassSecurityTrustHtml(counter(this.content || '', params).html.join(''));
}
}
9 changes: 8 additions & 1 deletion src/lib/layers/layers-text.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Component, HostBinding, Input, OnChanges, Optional, SimpleChanges } from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { Component, HostBinding, inject, Input, OnChanges, Optional, SimpleChanges } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import {
FlipProp,
Expand All @@ -10,9 +11,11 @@ import {
TextParams,
Transform,
} from '@fortawesome/fontawesome-svg-core';
import { FaConfig } from '../config';
import { faWarnIfParentNotExist } from '../shared/errors/warn-if-parent-not-exist';
import { FaProps } from '../shared/models/props.model';
import { faClassList } from '../shared/utils/classlist.util';
import { ensureCss } from '../shared/utils/css';
import { FaLayersComponent } from './layers.component';

@Component({
Expand All @@ -37,6 +40,9 @@ export class FaLayersTextComponent implements OnChanges {

@HostBinding('innerHTML') renderedHTML: SafeHtml;

private document = inject(DOCUMENT);
private config = inject(FaConfig);

constructor(
@Optional() private parent: FaLayersComponent,
private sanitizer: DomSanitizer,
Expand Down Expand Up @@ -75,6 +81,7 @@ export class FaLayersTextComponent implements OnChanges {
}

private updateContent(params: TextParams) {
ensureCss(this.document, this.config);
this.renderedHTML = this.sanitizer.bypassSecurityTrustHtml(text(this.content || '', params).html.join('\n'));
}
}
17 changes: 16 additions & 1 deletion src/lib/layers/layers.component.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
import { Component, ElementRef, HostBinding, Input, OnChanges, OnInit, Renderer2, SimpleChanges } from '@angular/core';
import { DOCUMENT } from '@angular/common';
import {
Component,
ElementRef,
HostBinding,
inject,
Input,
OnChanges,
OnInit,
Renderer2,
SimpleChanges,
} from '@angular/core';
import { SizeProp } from '@fortawesome/fontawesome-svg-core';
import { FaConfig } from '../config';
import { ensureCss } from '../shared/utils/css';

/**
* Fontawesome layers.
Expand All @@ -15,6 +27,8 @@ export class FaLayersComponent implements OnInit, OnChanges {

@Input() @HostBinding('class.fa-fw') fixedWidth?: boolean;

private document = inject(DOCUMENT);

constructor(
private renderer: Renderer2,
private elementRef: ElementRef,
Expand All @@ -23,6 +37,7 @@ export class FaLayersComponent implements OnInit, OnChanges {

ngOnInit() {
this.renderer.addClass(this.elementRef.nativeElement, 'fa-layers');
ensureCss(this.document, this.config);
this.fixedWidth = typeof this.fixedWidth === 'boolean' ? this.fixedWidth : this.config.fixedWidth;
}

Expand Down
Loading

0 comments on commit 5d26d6d

Please sign in to comment.