Skip to content

Commit

Permalink
feat: add dom shim so importing components in SSR works (#8184)
Browse files Browse the repository at this point in the history
  • Loading branch information
pskelin committed Jan 29, 2024
1 parent 786003a commit ca49674
Show file tree
Hide file tree
Showing 14 changed files with 171 additions and 9 deletions.
6 changes: 6 additions & 0 deletions packages/base/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -35,6 +40,7 @@
"prepublishOnly": "tsc"
},
"dependencies": {
"@lit-labs/ssr-dom-shim": "^1.1.2",
"lit-html": "^2.0.1"
},
"devDependencies": {
Expand Down
5 changes: 5 additions & 0 deletions packages/base/src/ManagedStyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down Expand Up @@ -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}"]`);
}
Expand Down
12 changes: 7 additions & 5 deletions packages/base/src/UI5Element.ts
Original file line number Diff line number Diff line change
@@ -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, {
Expand Down Expand Up @@ -338,17 +340,17 @@ 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));
elementTimeouts.set(localName, timeoutPromise);
}
await Promise.race([whenDefinedPromise, timeoutPromise]);
}
window.customElements.upgrade(child);
customElements.upgrade(child);
}
}

Expand Down Expand Up @@ -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;
Expand Down
12 changes: 12 additions & 0 deletions packages/base/src/ssr-dom-shim.ts
Original file line number Diff line number Diff line change
@@ -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;
1 change: 1 addition & 0 deletions packages/base/src/ssr-dom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// no shims in the browser when imported via conditional export
5 changes: 5 additions & 0 deletions packages/base/src/util/detectNavigatorLanguage.ts
Original file line number Diff line number Diff line change
@@ -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 = () => {
Expand Down
3 changes: 2 additions & 1 deletion packages/fiori/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
27 changes: 27 additions & 0 deletions packages/fiori/test/ssr/component-imports.js
Original file line number Diff line number Diff line change
@@ -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";
7 changes: 7 additions & 0 deletions packages/localization/src/sap/base/util/ObjectPath.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// ObjectPath is accessing window which breaks SSR, hence the overlay

const ObjectPath = {
set() {},
};

export default ObjectPath;
3 changes: 2 additions & 1 deletion packages/main/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
8 changes: 6 additions & 2 deletions packages/main/src/Toast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const MAX_DURATION = 1000;
const openedToasts: Array<Toast> = [];
let opener: HTMLElement | null;

let globalListenerAdded = false;
const handleGlobalKeydown = (e: KeyboardEvent) => {
const isCtrl = e.metaKey || (!isMac() && e.ctrlKey);
const isMKey = e.key.toLowerCase() === "m";
Expand All @@ -43,8 +44,6 @@ const handleGlobalKeydown = (e: KeyboardEvent) => {
}
};

document.addEventListener("keydown", handleGlobalKeydown);

/**
* @class
*
Expand Down Expand Up @@ -161,6 +160,11 @@ class Toast extends UI5Element {
super();

this._reopen = false;

if (!globalListenerAdded) {
document.addEventListener("keydown", handleGlobalKeydown);
globalListenerAdded = true;
}
}

onAfterRendering() {
Expand Down
83 changes: 83 additions & 0 deletions packages/main/test/ssr/component-imports.js
Original file line number Diff line number Diff line change
@@ -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";
3 changes: 3 additions & 0 deletions vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

0 comments on commit ca49674

Please sign in to comment.