diff --git a/packages/base/package.json b/packages/base/package.json index 15953c0f179f..5d4dedea7346 100644 --- a/packages/base/package.json +++ b/packages/base/package.json @@ -17,6 +17,11 @@ "directory": "packages/base" }, "exports": { + "./dist/ssr-dom.js": { + "browser": "./dist/ssr-dom.js", + "node": "./dist/ssr-dom-shim.js", + "default": "./dist/ssr-dom.js" + }, ".": "./index.js", "./dist/*": "./dist/*", "./package.json": "./package.json", @@ -35,6 +40,7 @@ "prepublishOnly": "tsc" }, "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.1.2", "lit-html": "^2.0.1" }, "devDependencies": { diff --git a/packages/base/src/ManagedStyles.ts b/packages/base/src/ManagedStyles.ts index 781cfa436d99..de347a2687cd 100644 --- a/packages/base/src/ManagedStyles.ts +++ b/packages/base/src/ManagedStyles.ts @@ -5,6 +5,8 @@ import { StyleData, StyleDataCSP } from "./types.js"; import { isSafari } from "./Device.js"; import { getCurrentRuntimeIndex, compareRuntimes } from "./Runtimes.js"; +const isSSR = typeof document === "undefined"; + const getStyleId = (name: string, value: string) => { return value ? `${name}|${value}` : name; }; @@ -106,6 +108,9 @@ const updateStyle = (data: StyleData, name: string, value = "", theme?: string) }; const hasStyle = (name: string, value = ""): boolean => { + if (isSSR) { + return true; + } if (shouldUseLinks()) { return !!document.querySelector(`head>link[${name}="${value}"]`); } diff --git a/packages/base/src/UI5Element.ts b/packages/base/src/UI5Element.ts index 240ca03f728e..895db8d3659d 100644 --- a/packages/base/src/UI5Element.ts +++ b/packages/base/src/UI5Element.ts @@ -1,3 +1,5 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import "@ui5/webcomponents-base/dist/ssr-dom.js"; import merge from "./thirdparty/merge.js"; import { boot } from "./Boot.js"; import UI5ElementMetadata, { @@ -338,9 +340,9 @@ abstract class UI5Element extends HTMLElement { const shouldWaitForCustomElement = localName.includes("-") && !shouldIgnoreCustomElement(localName); if (shouldWaitForCustomElement) { - const isDefined = window.customElements.get(localName); + const isDefined = customElements.get(localName); if (!isDefined) { - const whenDefinedPromise = window.customElements.whenDefined(localName); // Class registered, but instances not upgraded yet + const whenDefinedPromise = customElements.whenDefined(localName); // Class registered, but instances not upgraded yet let timeoutPromise = elementTimeouts.get(localName); if (!timeoutPromise) { timeoutPromise = new Promise(resolve => setTimeout(resolve, 1000)); @@ -348,7 +350,7 @@ abstract class UI5Element extends HTMLElement { } await Promise.race([whenDefinedPromise, timeoutPromise]); } - window.customElements.upgrade(child); + customElements.upgrade(child); } } @@ -1131,14 +1133,14 @@ abstract class UI5Element extends HTMLElement { const tag = this.getMetadata().getTag(); const definedLocally = isTagRegistered(tag); - const definedGlobally = window.customElements.get(tag); + const definedGlobally = customElements.get(tag); if (definedGlobally && !definedLocally) { recordTagRegistrationFailure(tag); } else if (!definedGlobally) { this._generateAccessors(); registerTag(tag); - window.customElements.define(tag, this as unknown as CustomElementConstructor); + customElements.define(tag, this as unknown as CustomElementConstructor); preloadLinks(this); } return this; diff --git a/packages/base/src/ssr-dom-shim.ts b/packages/base/src/ssr-dom-shim.ts new file mode 100644 index 000000000000..08744f1f1857 --- /dev/null +++ b/packages/base/src/ssr-dom-shim.ts @@ -0,0 +1,12 @@ +/* eslint-disable max-classes-per-file */ +import { HTMLElement, Element, customElements } from "@lit-labs/ssr-dom-shim"; + +globalThis.HTMLElement ??= HTMLElement; +globalThis.Element ??= Element; +globalThis.customElements ??= customElements; + +class NodeShim {} +globalThis.Node ??= NodeShim as object as typeof Node; + +class FileListShim {} +globalThis.FileList ??= FileListShim as object as typeof FileList; diff --git a/packages/base/src/ssr-dom.ts b/packages/base/src/ssr-dom.ts new file mode 100644 index 000000000000..8e0cb77ab223 --- /dev/null +++ b/packages/base/src/ssr-dom.ts @@ -0,0 +1 @@ +// no shims in the browser when imported via conditional export diff --git a/packages/base/src/util/detectNavigatorLanguage.ts b/packages/base/src/util/detectNavigatorLanguage.ts index 19e81af0a199..6a8177bd41e2 100644 --- a/packages/base/src/util/detectNavigatorLanguage.ts +++ b/packages/base/src/util/detectNavigatorLanguage.ts @@ -1,6 +1,11 @@ import { DEFAULT_LANGUAGE } from "../generated/AssetParameters.js"; +const isSSR = typeof document === "undefined"; + const detectNavigatorLanguage = () => { + if (isSSR) { + return DEFAULT_LANGUAGE; + } const browserLanguages = navigator.languages; const navigatorLanguage = () => { diff --git a/packages/fiori/package.json b/packages/fiori/package.json index d57eb6a0d695..06d3084ab298 100644 --- a/packages/fiori/package.json +++ b/packages/fiori/package.json @@ -35,7 +35,8 @@ "generate": "nps generate", "generateAPI": "nps generateAPI", "bundle": "nps build.bundle", - "test": "wc-dev test", + "test": "wc-dev test && yarn test:ssr", + "test:ssr": "node -e \"import('./test/ssr/component-imports.js')\"", "create-ui5-element": "wc-create-ui5-element", "prepublishOnly": "tsc" }, diff --git a/packages/fiori/test/ssr/component-imports.js b/packages/fiori/test/ssr/component-imports.js new file mode 100644 index 000000000000..6096e31c57b0 --- /dev/null +++ b/packages/fiori/test/ssr/component-imports.js @@ -0,0 +1,27 @@ +import Bar from "../../dist/Bar.js"; +// zxing is using window +// import BarcodeScannerDialog from "../../dist/BarcodeScannerDialog.js"; +import DynamicSideContent from "../../dist/DynamicSideContent.js"; +import FilterItem from "../../dist/FilterItem.js"; +import FilterItemOption from "../../dist/FilterItemOption.js"; +import FlexibleColumnLayout from "../../dist/FlexibleColumnLayout.js"; +import IllustratedMessage from "../../dist/IllustratedMessage.js"; +import MediaGallery from "../../dist/MediaGallery.js"; +import MediaGalleryItem from "../../dist/MediaGalleryItem.js"; +import NotificationAction from "../../dist/NotificationAction.js"; +import NotificationListGroupItem from "../../dist/NotificationListGroupItem.js"; +import NotificationListItem from "../../dist/NotificationListItem.js"; +import Page from "../../dist/Page.js"; +import ProductSwitch from "../../dist/ProductSwitch.js"; +import ProductSwitchItem from "../../dist/ProductSwitchItem.js"; +import ShellBar from "../../dist/ShellBar.js"; +import ShellBarItem from "../../dist/ShellBarItem.js"; +import SideNavigation from "../../dist/SideNavigation.js"; +import SideNavigationItem from "../../dist/SideNavigationItem.js"; +import SideNavigationSubItem from "../../dist/SideNavigationSubItem.js"; +import SortItem from "../../dist/SortItem.js"; +import Timeline from "../../dist/Timeline.js"; +import UploadCollection from "../../dist/UploadCollection.js"; +import UploadCollectionItem from "../../dist/UploadCollectionItem.js"; +import ViewSettingsDialog from "../../dist/ViewSettingsDialog.js"; +import Wizard from "../../dist/Wizard.js"; \ No newline at end of file diff --git a/packages/localization/src/sap/base/util/ObjectPath.ts b/packages/localization/src/sap/base/util/ObjectPath.ts new file mode 100644 index 000000000000..379e7229b150 --- /dev/null +++ b/packages/localization/src/sap/base/util/ObjectPath.ts @@ -0,0 +1,7 @@ +// ObjectPath is accessing window which breaks SSR, hence the overlay + +const ObjectPath = { + set() {}, +}; + +export default ObjectPath; diff --git a/packages/main/package.json b/packages/main/package.json index fbaa033283b6..439fb773e884 100644 --- a/packages/main/package.json +++ b/packages/main/package.json @@ -26,7 +26,8 @@ "build": "wc-dev build", "bundle": "nps build.bundle", "test": "wc-dev test", - "test:suite-1": "wc-dev test-suite-1", + "test:ssr": "node -e \"import('./test/ssr/component-imports.js')\"", + "test:suite-1": "wc-dev test-suite-1 && yarn test:ssr", "test:suite-2": "wc-dev test-suite-2", "create-ui5-element": "wc-create-ui5-element", "prepublishOnly": "tsc" diff --git a/packages/main/src/Toast.ts b/packages/main/src/Toast.ts index 511b14c88d40..a93bb2408c9e 100644 --- a/packages/main/src/Toast.ts +++ b/packages/main/src/Toast.ts @@ -20,6 +20,7 @@ const MAX_DURATION = 1000; const openedToasts: Array = []; let opener: HTMLElement | null; +let globalListenerAdded = false; const handleGlobalKeydown = (e: KeyboardEvent) => { const isCtrl = e.metaKey || (!isMac() && e.ctrlKey); const isMKey = e.key.toLowerCase() === "m"; @@ -43,8 +44,6 @@ const handleGlobalKeydown = (e: KeyboardEvent) => { } }; -document.addEventListener("keydown", handleGlobalKeydown); - /** * @class * @@ -161,6 +160,11 @@ class Toast extends UI5Element { super(); this._reopen = false; + + if (!globalListenerAdded) { + document.addEventListener("keydown", handleGlobalKeydown); + globalListenerAdded = true; + } } onAfterRendering() { diff --git a/packages/main/test/ssr/component-imports.js b/packages/main/test/ssr/component-imports.js new file mode 100644 index 000000000000..2d8412017afd --- /dev/null +++ b/packages/main/test/ssr/component-imports.js @@ -0,0 +1,83 @@ +import Avatar from "../../dist/Avatar.js"; +import AvatarGroup from "../../dist/AvatarGroup.js"; +import Badge from "../../dist/Badge.js"; +import Breadcrumbs from "../../dist/Breadcrumbs.js"; +import BusyIndicator from "../../dist/BusyIndicator.js"; +import Button from "../../dist/Button.js"; +import Card from "../../dist/Card.js"; +import CardHeader from "../../dist/CardHeader.js"; +import Carousel from "../../dist/Carousel.js"; +import CheckBox from "../../dist/CheckBox.js"; +import ColorPalette from "../../dist/ColorPalette.js"; +import ColorPaletteItem from "../../dist/ColorPaletteItem.js"; +import ColorPalettePopover from "../../dist/ColorPalettePopover.js"; +import ColorPicker from "../../dist/ColorPicker.js"; +import ComboBox from "../../dist/ComboBox.js"; +import DatePicker from "../../dist/DatePicker.js"; +import DateRangePicker from "../../dist/DateRangePicker.js"; +import DateTimePicker from "../../dist/DateTimePicker.js"; +import Dialog from "../../dist/Dialog.js"; +import FileUploader from "../../dist/FileUploader.js"; +import Icon from "../../dist/Icon.js"; +import Input from "../../dist/Input.js"; +import MultiInput from "../../dist/MultiInput.js"; +import Label from "../../dist/Label.js"; +import Link from "../../dist/Link.js"; +import Menu from "../../dist/Menu.js"; +import NavigationMenu from "../../dist/NavigationMenu.js"; +import NavigationMenuItem from "../../dist/NavigationMenuItem.js"; +import MenuItem from "../../dist/MenuItem.js"; +import Popover from "../../dist/Popover.js"; +import Panel from "../../dist/Panel.js"; +import RadioButton from "../../dist/RadioButton.js"; +import ResponsivePopover from "../../dist/ResponsivePopover.js"; +import SegmentedButton from "../../dist/SegmentedButton.js"; +import SegmentedButtonItem from "../../dist/SegmentedButtonItem.js"; +import Select from "../../dist/Select.js"; +import SelectMenu from "../../dist/SelectMenu.js"; +import SelectMenuOption from "../../dist/SelectMenuOption.js"; +import Slider from "../../dist/Slider.js"; +import SplitButton from "../../dist/SplitButton.js"; +import StepInput from "../../dist/StepInput.js"; +import RangeSlider from "../../dist/RangeSlider.js"; +import Switch from "../../dist/Switch.js"; +import MessageStrip from "../../dist/MessageStrip.js"; +import MultiComboBox from "../../dist/MultiComboBox.js"; +import ProgressIndicator from "../../dist/ProgressIndicator.js"; +import RatingIndicator from "../../dist/RatingIndicator.js"; +import TabContainer from "../../dist/TabContainer.js"; +import Tab from "../../dist/Tab.js"; +import TabSeparator from "../../dist/TabSeparator.js"; +import Table from "../../dist/Table.js"; +import TableColumn from "../../dist/TableColumn.js"; +import TableRow from "../../dist/TableRow.js"; +import TableGroupRow from "../../dist/TableGroupRow.js"; +import TableCell from "../../dist/TableCell.js"; +import TextArea from "../../dist/TextArea.js"; +import TimeSelection from "../../dist/TimeSelection.js"; +import TimePicker from "../../dist/TimePicker.js"; +import TimePickerClock from "../../dist/TimePickerClock.js"; +import TimeSelectionClocks from "../../dist/TimeSelectionClocks.js"; +import Title from "../../dist/Title.js"; +import Toast from "../../dist/Toast.js"; +import ToggleButton from "../../dist/ToggleButton.js"; +// console.log({ToggleButton}) +import Toolbar from "../../dist/Toolbar.js"; +import ToolbarButton from "../../dist/ToolbarButton.js"; +import ToolbarSeparator from "../../dist/ToolbarSeparator.js"; +import ToolbarSpacer from "../../dist/ToolbarSpacer.js"; +import ToolbarSelect from "../../dist/ToolbarSelect.js"; +import Tree from "../../dist/Tree.js"; +import TreeList from "../../dist/TreeList.js"; +import TreeItem from "../../dist/TreeItem.js"; +import TreeItemCustom from "../../dist/TreeItemCustom.js"; +import List from "../../dist/List.js"; +// console.log({List}) +import StandardListItem from "../../dist/StandardListItem.js"; +import CustomListItem from "../../dist/CustomListItem.js"; +import GroupHeaderListItem from "../../dist/GroupHeaderListItem.js"; + +// Features +import "../../dist/features/InputElementsFormSupport.js"; +import "../../dist/features/ColorPaletteMoreColors.js"; +import "../../dist/features/InputSuggestions.js"; diff --git a/vite.config.js b/vite.config.js index b3563a55830f..bf2be517625d 100644 --- a/vite.config.js +++ b/vite.config.js @@ -63,6 +63,9 @@ const customResolver = (id, source, options) => { if (resolved.endsWith("dist/sap/base/util/LoaderExtensions.js")) { resolved = resolved.replace("/dist/", "/src/").replace(".js", ".ts"); } + if (resolved.endsWith("dist/sap/base/util/ObjectPath.js")) { + resolved = resolved.replace("/dist/", "/src/").replace(".js", ".ts"); + } return resolved; } } diff --git a/yarn.lock b/yarn.lock index bf01fe5d1d29..835bd85303a5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2051,6 +2051,11 @@ resolved "https://registry.yarnpkg.com/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.1.1.tgz#64df34e2f12e68e78ac57e571d25ec07fa460ca9" integrity sha512-kXOeFbfCm4fFf2A3WwVEeQj55tMZa8c8/f9AKHMobQMkzNUfUj+antR3fRPaZJawsa1aZiP/Da3ndpZrwEe4rQ== +"@lit-labs/ssr-dom-shim@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.1.2.tgz#d693d972974a354034454ec1317eb6afd0b00312" + integrity sha512-jnOD+/+dSrfTWYfSXBXlo5l5f0q1UuJo3tkbMDCYA2lKUYq79jaxqtGEvnRoh049nt1vdo1+45RinipU6FGY2g== + "@lit/reactive-element@^1.3.0", "@lit/reactive-element@^1.6.0": version "1.6.2" resolved "https://registry.yarnpkg.com/@lit/reactive-element/-/reactive-element-1.6.2.tgz#c256690f82f2d7d0ffb0b1cdf68dcb1ec86cea28"