diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 4918acf..9f27399 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -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 @@ -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 diff --git a/.gitignore b/.gitignore index 63124d0..02e6b1d 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,4 @@ Thumbs.db !/.yarn/plugins !/.yarn/sdks !/.yarn/versions +!/.yarn/patches diff --git a/.yarn/patches/@angular-devkit-build-angular-npm-17.3.7-60e65bd832.patch b/.yarn/patches/@angular-devkit-build-angular-npm-17.3.7-60e65bd832.patch new file mode 100644 index 0000000..03124f9 --- /dev/null +++ b/.yarn/patches/@angular-devkit-build-angular-npm-17.3.7-60e65bd832.patch @@ -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; + } diff --git a/angular.json b/angular.json index 2e6cb68..e7a4f67 100644 --- a/angular.json +++ b/angular.json @@ -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" diff --git a/docs/guide/adding-css.md b/docs/guide/adding-css.md new file mode 100644 index 0000000..f91fa62 --- /dev/null +++ b/docs/guide/adding-css.md @@ -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. diff --git a/docs/upgrading/0.14.0-0.15.0.md b/docs/upgrading/0.14.0-0.15.0.md index f57a0ce..686cb43 100644 --- a/docs/upgrading/0.14.0-0.15.0.md +++ b/docs/upgrading/0.14.0-0.15.0.md @@ -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. diff --git a/docs/usage.md b/docs/usage.md index 06dc8d9..fd2d005 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -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) diff --git a/package.json b/package.json index 94516f8..99562c0 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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", diff --git a/projects/demo/e2e/src/app.e2e-spec.ts b/projects/demo/e2e/src/app.e2e-spec.ts index 4223704..82d4e7c 100644 --- a/projects/demo/e2e/src/app.e2e-spec.ts +++ b/projects/demo/e2e/src/app.e2e-spec.ts @@ -1,4 +1,4 @@ -import { browser, logging } from 'protractor'; +import { browser, ElementFinder, logging } from 'protractor'; import { appPage } from './app.page'; describe('Angular FontAwesome demo', () => { @@ -6,12 +6,38 @@ describe('Angular FontAwesome demo', () => { // 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); diff --git a/projects/demo/e2e/src/app.page.ts b/projects/demo/e2e/src/app.page.ts index 8071cac..a47e534 100644 --- a/projects/demo/e2e/src/app.page.ts +++ b/projects/demo/e2e/src/app.page.ts @@ -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); diff --git a/src/lib/config.ts b/src/lib/config.ts index 9634ecd..6f4f6c3 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -1,4 +1,5 @@ import { Injectable } from '@angular/core'; +import { config } from '@fortawesome/fontawesome-svg-core'; import { IconDefinition, IconPrefix } from './types'; @Injectable({ providedIn: 'root' }) @@ -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; } diff --git a/src/lib/icon/icon.component.ts b/src/lib/icon/icon.component.ts index bf00db7..50c608f 100644 --- a/src/lib/icon/icon.component.ts +++ b/src/lib/icon/icon.component.ts @@ -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, @@ -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'; @@ -71,6 +73,8 @@ export class FaIconComponent implements OnChanges { @HostBinding('innerHTML') renderedIconHTML: SafeHtml; + private document = inject(DOCUMENT); + constructor( private sanitizer: DomSanitizer, private config: FaConfig, @@ -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')); } diff --git a/src/lib/layers/layers-counter.component.ts b/src/lib/layers/layers-counter.component.ts index 4a56fcd..ac3a997 100644 --- a/src/lib/layers/layers-counter.component.ts +++ b/src/lib/layers/layers-counter.component.ts @@ -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({ @@ -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, @@ -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('')); } } diff --git a/src/lib/layers/layers-text.component.ts b/src/lib/layers/layers-text.component.ts index 71aa223..8617c18 100644 --- a/src/lib/layers/layers-text.component.ts +++ b/src/lib/layers/layers-text.component.ts @@ -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, @@ -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({ @@ -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, @@ -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')); } } diff --git a/src/lib/layers/layers.component.ts b/src/lib/layers/layers.component.ts index 76c9af4..c61dd08 100644 --- a/src/lib/layers/layers.component.ts +++ b/src/lib/layers/layers.component.ts @@ -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. @@ -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, @@ -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; } diff --git a/src/lib/shared/utils/css.ts b/src/lib/shared/utils/css.ts new file mode 100644 index 0000000..c92e1f8 --- /dev/null +++ b/src/lib/shared/utils/css.ts @@ -0,0 +1,63 @@ +import { dom } from '@fortawesome/fontawesome-svg-core'; +import { FaConfig } from '../../config'; + +const cssInserted = new WeakSet(); +export const autoCssId = 'fa-auto-css'; + +/** + * Ensure that Font Awesome CSS is inserted into the page. + * + * SVG Core has the same logic to insert the same styles into the page, however + * it's not aware of Angular SSR and therefore styles won't be added in that + * context leading to https://github.com/FortAwesome/angular-fontawesome/issues/48. + * That's why the same logic is duplicated here. + * + * @param document - Document. + * @param config - Font Awesome configuration. + */ +export function ensureCss(document: Document, config: FaConfig): void { + if (!config.autoAddCss) { + return; + } + + if (cssInserted.has(document)) { + return; + } + + // Prevent adding the same styles again after hydration. + if (document.getElementById(autoCssId) != null) { + config.autoAddCss = false; + cssInserted.add(document); + return; + } + + const style = document.createElement('style'); + style.setAttribute('type', 'text/css'); + style.setAttribute('id', autoCssId); + style.innerHTML = dom.css(); + const headChildren = document.head.childNodes; + let beforeChild = null; + + for (let i = headChildren.length - 1; i > -1; i--) { + const child = headChildren[i]; + const tagName = child.nodeName.toUpperCase(); + + if (['STYLE', 'LINK'].indexOf(tagName) > -1) { + beforeChild = child; + } + } + + document.head.insertBefore(style, beforeChild); + + // Prevent SVG Core from adding the same styles. + // + // As the logic is present in two places and SVG Core is not aware about + // this library, it may lead to styles being added twice. This can only + // occur when icon is rendered by SVG Core before the Angular component + // and should not have any significant negative impact. This is a rare + // use case, and it's tricky to prevent, so we accept this behavior. Consumer + // can choose to disable `FaConfig.autoAddCss` and add styles manually to + // prevent this from happening. + config.autoAddCss = false; + cssInserted.add(document); +} diff --git a/yarn.lock b/yarn.lock index 926bc91..2dfc6a8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -42,7 +42,7 @@ __metadata: languageName: node linkType: hard -"@angular-devkit/build-angular@npm:^17.3.7": +"@angular-devkit/build-angular@npm:17.3.7": version: 17.3.7 resolution: "@angular-devkit/build-angular@npm:17.3.7" dependencies: @@ -154,6 +154,118 @@ __metadata: languageName: node linkType: hard +"@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": + version: 17.3.7 + resolution: "@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::version=17.3.7&hash=0bfa07" + dependencies: + "@ampproject/remapping": "npm:2.3.0" + "@angular-devkit/architect": "npm:0.1703.7" + "@angular-devkit/build-webpack": "npm:0.1703.7" + "@angular-devkit/core": "npm:17.3.7" + "@babel/core": "npm:7.24.0" + "@babel/generator": "npm:7.23.6" + "@babel/helper-annotate-as-pure": "npm:7.22.5" + "@babel/helper-split-export-declaration": "npm:7.22.6" + "@babel/plugin-transform-async-generator-functions": "npm:7.23.9" + "@babel/plugin-transform-async-to-generator": "npm:7.23.3" + "@babel/plugin-transform-runtime": "npm:7.24.0" + "@babel/preset-env": "npm:7.24.0" + "@babel/runtime": "npm:7.24.0" + "@discoveryjs/json-ext": "npm:0.5.7" + "@ngtools/webpack": "npm:17.3.7" + "@vitejs/plugin-basic-ssl": "npm:1.1.0" + ansi-colors: "npm:4.1.3" + autoprefixer: "npm:10.4.18" + babel-loader: "npm:9.1.3" + babel-plugin-istanbul: "npm:6.1.1" + browserslist: "npm:^4.21.5" + copy-webpack-plugin: "npm:11.0.0" + critters: "npm:0.0.22" + css-loader: "npm:6.10.0" + esbuild: "npm:0.20.1" + esbuild-wasm: "npm:0.20.1" + fast-glob: "npm:3.3.2" + http-proxy-middleware: "npm:2.0.6" + https-proxy-agent: "npm:7.0.4" + inquirer: "npm:9.2.15" + jsonc-parser: "npm:3.2.1" + karma-source-map-support: "npm:1.4.0" + less: "npm:4.2.0" + less-loader: "npm:11.1.0" + license-webpack-plugin: "npm:4.0.2" + loader-utils: "npm:3.2.1" + magic-string: "npm:0.30.8" + mini-css-extract-plugin: "npm:2.8.1" + mrmime: "npm:2.0.0" + open: "npm:8.4.2" + ora: "npm:5.4.1" + parse5-html-rewriting-stream: "npm:7.0.0" + picomatch: "npm:4.0.1" + piscina: "npm:4.4.0" + postcss: "npm:8.4.35" + postcss-loader: "npm:8.1.1" + resolve-url-loader: "npm:5.0.0" + rxjs: "npm:7.8.1" + sass: "npm:1.71.1" + sass-loader: "npm:14.1.1" + semver: "npm:7.6.0" + source-map-loader: "npm:5.0.0" + source-map-support: "npm:0.5.21" + terser: "npm:5.29.1" + tree-kill: "npm:1.2.2" + tslib: "npm:2.6.2" + undici: "npm:6.11.1" + vite: "npm:5.1.7" + watchpack: "npm:2.4.0" + webpack: "npm:5.90.3" + webpack-dev-middleware: "npm:6.1.2" + webpack-dev-server: "npm:4.15.1" + webpack-merge: "npm:5.10.0" + webpack-subresource-integrity: "npm:5.1.0" + peerDependencies: + "@angular/compiler-cli": ^17.0.0 + "@angular/localize": ^17.0.0 + "@angular/platform-server": ^17.0.0 + "@angular/service-worker": ^17.0.0 + "@web/test-runner": ^0.18.0 + browser-sync: ^3.0.2 + jest: ^29.5.0 + jest-environment-jsdom: ^29.5.0 + karma: ^6.3.0 + ng-packagr: ^17.0.0 + protractor: ^7.0.0 + tailwindcss: ^2.0.0 || ^3.0.0 + typescript: ">=5.2 <5.5" + dependenciesMeta: + esbuild: + optional: true + peerDependenciesMeta: + "@angular/localize": + optional: true + "@angular/platform-server": + optional: true + "@angular/service-worker": + optional: true + "@web/test-runner": + optional: true + browser-sync: + optional: true + jest: + optional: true + jest-environment-jsdom: + optional: true + karma: + optional: true + ng-packagr: + optional: true + protractor: + optional: true + tailwindcss: + optional: true + checksum: a25c94bc2df64b7aeeb288fe76665aa6148d3b0427052e206e5c1bb161301033fdf3a5103993fbac221fd0e2c50aae9c62729b16d9b8f405afbd1d40f579072c + languageName: node + linkType: hard + "@angular-devkit/build-webpack@npm:0.1703.7": version: 0.1703.7 resolution: "@angular-devkit/build-webpack@npm:0.1703.7" @@ -2647,7 +2759,7 @@ __metadata: version: 0.0.0-use.local resolution: "@fortawesome/angular-fontawesome@workspace:." dependencies: - "@angular-devkit/build-angular": "npm:^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": "npm:^17.3.7" "@angular-devkit/schematics": "npm:^17.3.7" "@angular-eslint/builder": "npm:^17.0.0"