From 02db939e89ca918b93aca252ff82aa6346f7e56f Mon Sep 17 00:00:00 2001 From: danielwiehl Date: Fri, 11 Aug 2023 07:59:41 +0200 Subject: [PATCH] fix(workbench): fetch icon font for applications deployed in a subdirectory Previously, the workbench icon font was not loaded for applications deployed in a subdirectory because the path to the icon font was absolute and not root-relative, preventing the HTML base mechanism from kicking in. closes #466 --- .github/workflows/workflow.yml | 7 ++++ angular.json | 35 +++++++++++++++++++ package.json | 3 ++ .../scion/e2e-testing/playwright.config.ts | 5 +++ projects/scion/e2e-testing/src/app.po.ts | 30 +++++++++------- .../src/workbench/basehref.e2e-spec.ts | 33 +++++++++++++++++ projects/scion/workbench/_index.scss | 2 +- projects/scion/workbench/theme/_icons.scss | 15 +++++--- 8 files changed, 112 insertions(+), 18 deletions(-) create mode 100644 projects/scion/e2e-testing/src/workbench/basehref.e2e-spec.ts diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 795c12b25..4b1c57c35 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -91,6 +91,8 @@ jobs: app: - name: workbench-testing-app-ci cmd: npm run workbench-testing-app:ci:build + - name: workbench-testing-app-basehref + cmd: npm run workbench-testing-app:basehref:build - name: workbench-testing-app-vercel cmd: npm run workbench-testing-app:vercel:build - name: workbench-client-testing-app-ci @@ -177,6 +179,11 @@ jobs: with: name: workbench-testing-app-ci path: dist/workbench-testing-app-ci + - name: 'Downloading app: workbench-testing-app-basehref (dist)' + uses: actions/download-artifact@v2 + with: + name: workbench-testing-app-basehref + path: dist/workbench-testing-app-basehref - name: 'Downloading app: workbench-client-testing-app-ci (dist)' uses: actions/download-artifact@v2 with: diff --git a/angular.json b/angular.json index 6d5cec015..fffd5e954 100644 --- a/angular.json +++ b/angular.json @@ -292,6 +292,29 @@ "outputHashing": "all", "outputPath": "dist/workbench-testing-app-ci" }, + "production-basehref": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "fileReplacements": [ + { + "replace": "apps/workbench-testing-app/src/environments/environment.ts", + "with": "apps/workbench-testing-app/src/environments/environment.ci.ts" + } + ], + "outputHashing": "all", + "outputPath": "dist/workbench-testing-app-basehref/subdir", + "baseHref": "/subdir/" + }, "development": { "buildOptimizer": false, "optimization": false, @@ -299,6 +322,15 @@ "extractLicenses": false, "sourceMap": true, "namedChunks": true + }, + "development-basehref": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true, + "baseHref": "/subdir/" } }, "defaultConfiguration": "development" @@ -311,6 +343,9 @@ }, "development": { "browserTarget": "workbench-testing-app:build:development" + }, + "development-basehref": { + "browserTarget": "workbench-testing-app:build:development-basehref" } }, "defaultConfiguration": "development" diff --git a/package.json b/package.json index 03ccdd8ef..2d8193dc8 100644 --- a/package.json +++ b/package.json @@ -38,9 +38,12 @@ "workbench-testing-app:build": "ng build workbench-testing-app --configuration=development", "workbench-testing-app:vercel:build": "ng build workbench-testing-app --configuration=production-vercel", "workbench-testing-app:ci:build": "ng build workbench-testing-app --configuration=production-ci", + "workbench-testing-app:basehref:build": "ng build workbench-testing-app --configuration=production-basehref", "workbench-testing-app:lint": "ng lint workbench-testing-app", "workbench-testing-app:serve": "ng serve workbench-testing-app --port 4200", + "workbench-testing-app:basehref:serve": "ng serve workbench-testing-app --configuration=development-basehref --port 4300", "workbench-testing-app:http-server": "cd dist/workbench-testing-app-ci && http-server --silent --port 4200", + "workbench-testing-app:basehref:http-server": "cd dist/workbench-testing-app-basehref && http-server --silent --port 4300", "workbench-client-testing-app:build": "ng build workbench-client-testing-app --configuration=development", "workbench-client-testing-app:vercel:build": "ng build workbench-client-testing-app --configuration=production-vercel", diff --git a/projects/scion/e2e-testing/playwright.config.ts b/projects/scion/e2e-testing/playwright.config.ts index ce7c47bb3..e48c52473 100644 --- a/projects/scion/e2e-testing/playwright.config.ts +++ b/projects/scion/e2e-testing/playwright.config.ts @@ -33,6 +33,11 @@ export default { port: 4202, reuseExistingServer: !runInCI, }, + { + command: runInCI ? 'npm run workbench-testing-app:basehref:http-server' : 'npm run workbench-testing-app:basehref:serve', + port: 4300, + reuseExistingServer: !runInCI, + }, ], use: { browserName: 'chromium', diff --git a/projects/scion/e2e-testing/src/app.po.ts b/projects/scion/e2e-testing/src/app.po.ts index 45d6c5cc9..674f31e2f 100644 --- a/projects/scion/e2e-testing/src/app.po.ts +++ b/projects/scion/e2e-testing/src/app.po.ts @@ -44,24 +44,24 @@ export class AppPO { * * By passing a features object, you can control how to start the workbench and which app features to enable. */ - public async navigateTo(features?: Features): Promise { + public async navigateTo(options?: Options): Promise { this._workbenchStartupQueryParams = new URLSearchParams(); - this._workbenchStartupQueryParams.append(WorkenchStartupQueryParams.LAUNCHER, features?.launcher ?? 'LAZY'); - this._workbenchStartupQueryParams.append(WorkenchStartupQueryParams.STANDALONE, `${(features?.microfrontendSupport ?? true) === false}`); - this._workbenchStartupQueryParams.append(WorkenchStartupQueryParams.CONFIRM_STARTUP, `${features?.confirmStartup ?? false}`); - this._workbenchStartupQueryParams.append(WorkenchStartupQueryParams.SIMULATE_SLOW_CAPABILITY_LOOKUP, `${features?.simulateSlowCapabilityLookup ?? false}`); - this._workbenchStartupQueryParams.append(WorkenchStartupQueryParams.PERSPECTIVES, `${(features?.perspectives ?? []).join(';')}`); + this._workbenchStartupQueryParams.append(WorkenchStartupQueryParams.LAUNCHER, options?.launcher ?? 'LAZY'); + this._workbenchStartupQueryParams.append(WorkenchStartupQueryParams.STANDALONE, `${(options?.microfrontendSupport ?? true) === false}`); + this._workbenchStartupQueryParams.append(WorkenchStartupQueryParams.CONFIRM_STARTUP, `${options?.confirmStartup ?? false}`); + this._workbenchStartupQueryParams.append(WorkenchStartupQueryParams.SIMULATE_SLOW_CAPABILITY_LOOKUP, `${options?.simulateSlowCapabilityLookup ?? false}`); + this._workbenchStartupQueryParams.append(WorkenchStartupQueryParams.PERSPECTIVES, `${(options?.perspectives ?? []).join(';')}`); const featureQueryParams = new URLSearchParams(); - if (features?.stickyStartViewTab !== undefined) { - featureQueryParams.append('stickyStartViewTab', `${features.stickyStartViewTab}`); + if (options?.stickyStartViewTab !== undefined) { + featureQueryParams.append('stickyStartViewTab', `${options.stickyStartViewTab}`); } - if (features?.showNewTabAction !== undefined) { - featureQueryParams.append('showNewTabAction', `${features.showNewTabAction}`); + if (options?.showNewTabAction !== undefined) { + featureQueryParams.append('showNewTabAction', `${options.showNewTabAction}`); } - await this.page.goto(`/?${this._workbenchStartupQueryParams.toString()}#/${featureQueryParams.toString() ? `?${featureQueryParams.toString()}` : ''}`); + await this.page.goto(`${options?.url ?? ''}/?${this._workbenchStartupQueryParams.toString()}#/${featureQueryParams.toString() ? `?${featureQueryParams.toString()}` : ''}`); // Wait until the workbench completed startup. await this.waitUntilWorkbenchStarted(); } @@ -264,9 +264,13 @@ export class AppPO { } /** - * Configures features of the testing app. + * Configures options to start the testing app. */ -export interface Features { +export interface Options { + /** + * Specifies the URL to load into the browser. If not set, defaults to the `baseURL` as specified in `playwright.config.ts`. + */ + url?: string; /** * Controls launching of the testing app. By default, if not specified, starts the workbench lazy. */ diff --git a/projects/scion/e2e-testing/src/workbench/basehref.e2e-spec.ts b/projects/scion/e2e-testing/src/workbench/basehref.e2e-spec.ts new file mode 100644 index 000000000..1c258b4e1 --- /dev/null +++ b/projects/scion/e2e-testing/src/workbench/basehref.e2e-spec.ts @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018-2023 Swiss Federal Railways + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import {expect} from '@playwright/test'; +import {test} from '../fixtures'; + +test.describe('HTML base HREF', () => { + + /** + * This test expects the application to be deployed on 'http://localhost:4300/subdir' with '/subdir/' configured as the base URL. + * + * Start the app using the following command: + * + * ``` + * npm run workbench-testing-app:basehref:serve + * ``` + */ + test('should fetch the icon font if deployed in a subdirectory', async ({page, appPO}) => { + const response = page.waitForResponse(/scion-workbench-icons\.(ttf|woff)/); + await appPO.navigateTo({url: 'http://localhost:4300/subdir', microfrontendSupport: false}); + + // Expect the icon font to be loaded. + const iconFontLoaded = (await response).ok(); + await expect(iconFontLoaded).toBe(true); + }); +}); diff --git a/projects/scion/workbench/_index.scss b/projects/scion/workbench/_index.scss index 27c63d710..2156bea7d 100644 --- a/projects/scion/workbench/_index.scss +++ b/projects/scion/workbench/_index.scss @@ -18,7 +18,7 @@ * @use '@scion/workbench' with ( * $theme: ( * icon-font: ( - * directory: '/path/to/font', // defaults to '/assets/fonts' if omitted + * directory: 'path/to/font', // defaults to 'assets/fonts' if omitted * filename: 'icons' // defaults to 'scion-workbench-icons' if omitted * ) * ) diff --git a/projects/scion/workbench/theme/_icons.scss b/projects/scion/workbench/theme/_icons.scss index ed83c2ee8..ea84814f6 100644 --- a/projects/scion/workbench/theme/_icons.scss +++ b/projects/scion/workbench/theme/_icons.scss @@ -18,7 +18,8 @@ $version: 2; $default-icon-font-config: ( - directory: '/assets/fonts', + // Path should be "root relative" to support HTML base href. + directory: 'assets/fonts', filename: 'scion-workbench-icons', version: $version, ); @@ -40,9 +41,15 @@ $default-icon-font-config: ( @font-face { font-family: 'scion-workbench-icons'; - src: url($path + '.ttf?' + $version) format('truetype'), - url($path + '.woff?' + $version) format('woff'), - url($path + '.svg?' + $version + '#scion-workbench-icons') format('svg'); + // Building the application fails if using relative URLs to reference font files because they are not relative to "this" SASS file + // but relative to the app root. Therefore, we instruct the bundler not to process font files by prefixing their path with a caret `^`. + // Note that the caret is not officially documented and should be tested with each Angular release. + // See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base + // See https://github.com/angular/angular/issues/32811 + // See https://github.com/angular/angular-cli/issues/14587 + src: url('^' + $path + '.ttf?' + $version) format('truetype'), + url('^' + $path + '.woff?' + $version) format('woff'), + url('^' + $path + '.svg?' + $version + '#scion-workbench-icons') format('svg'); font-weight: normal; font-style: normal; font-display: block;