diff --git a/.changeset/shiny-owls-rhyme.md b/.changeset/shiny-owls-rhyme.md new file mode 100644 index 0000000000..42a7e95e52 --- /dev/null +++ b/.changeset/shiny-owls-rhyme.md @@ -0,0 +1,6 @@ +--- +'@shopify/ui-extensions-react': minor +'@shopify/ui-extensions': minor +--- + +wip diff --git a/packages/ui-extensions-react/package.json b/packages/ui-extensions-react/package.json index d7523dc844..77af5681fb 100644 --- a/packages/ui-extensions-react/package.json +++ b/packages/ui-extensions-react/package.json @@ -63,6 +63,8 @@ "dependencies": { "@remote-ui/async-subscription": "^2.1.12", "@remote-ui/react": "^5.0.2", + "@remote-dom/react": "^1.2.1", + "react-dom": "^18.3.1", "@types/react": ">=18.2.67" }, "peerDependencies": { @@ -81,6 +83,7 @@ "@faker-js/faker": "^8.4.1", "@quilted/react-testing": "^0.5.31", "@shopify/ui-extensions": "0.0.0-unstable", + "@types/react-dom": "^18.3.0", "react": "^18.0.0", "react-reconciler": "0.29.0", "react-test-renderer": "^18.2.0" diff --git a/packages/ui-extensions-react/src/surfaces/checkout/components/Button/Button.ts b/packages/ui-extensions-react/src/surfaces/checkout/components/Button/Button.ts index c4d8b1346c..1a3c2837b9 100644 --- a/packages/ui-extensions-react/src/surfaces/checkout/components/Button/Button.ts +++ b/packages/ui-extensions-react/src/surfaces/checkout/components/Button/Button.ts @@ -1,9 +1,13 @@ import {Button as BaseButton} from '@shopify/ui-extensions/checkout'; -import {createRemoteReactComponent} from '@remote-ui/react'; import type {ReactPropsFromRemoteComponentType} from '@remote-ui/react'; +import {createRemoteComponent} from '@remote-dom/react'; +// @ts-ignore export type ButtonProps = ReactPropsFromRemoteComponentType; -export const Button = createRemoteReactComponent(BaseButton, { - fragmentProps: ['overlay'], +export const Button = createRemoteComponent('ui-button', BaseButton, { + eventProps: { + onPress: {event: 'press'}, + }, + slots: ['overlay'], }); diff --git a/packages/ui-extensions-react/src/surfaces/checkout/components/Link/Link.ts b/packages/ui-extensions-react/src/surfaces/checkout/components/Link/Link.ts index 219285bc82..dd9cc50fbf 100644 --- a/packages/ui-extensions-react/src/surfaces/checkout/components/Link/Link.ts +++ b/packages/ui-extensions-react/src/surfaces/checkout/components/Link/Link.ts @@ -1,9 +1,7 @@ import {Link as BaseLink} from '@shopify/ui-extensions/checkout'; -import {createRemoteReactComponent} from '@remote-ui/react'; import type {ReactPropsFromRemoteComponentType} from '@remote-ui/react'; +import {createRemoteComponent} from '@remote-dom/react'; export type LinkProps = ReactPropsFromRemoteComponentType; -export const Link = createRemoteReactComponent(BaseLink, { - fragmentProps: ['overlay'], -}); +export const Link = createRemoteComponent('ui-link', BaseLink); diff --git a/packages/ui-extensions-react/src/surfaces/checkout/components/Modal/Modal.ts b/packages/ui-extensions-react/src/surfaces/checkout/components/Modal/Modal.ts index 7933db7a8f..d5627cee98 100644 --- a/packages/ui-extensions-react/src/surfaces/checkout/components/Modal/Modal.ts +++ b/packages/ui-extensions-react/src/surfaces/checkout/components/Modal/Modal.ts @@ -1,9 +1,8 @@ import {Modal as BaseModal} from '@shopify/ui-extensions/checkout'; -import {createRemoteReactComponent} from '@remote-ui/react'; import type {ReactPropsFromRemoteComponentType} from '@remote-ui/react'; +import {createRemoteComponent} from '@remote-dom/react'; +// @ts-ignore export type ModalProps = ReactPropsFromRemoteComponentType; -export const Modal = createRemoteReactComponent(BaseModal, { - fragmentProps: ['primaryAction', 'secondaryActions'], -}); +export const Modal = createRemoteComponent('ui-modal', BaseModal); diff --git a/packages/ui-extensions-react/src/surfaces/checkout/components/View/View.ts b/packages/ui-extensions-react/src/surfaces/checkout/components/View/View.ts index a8151f805b..969d3b8e84 100644 --- a/packages/ui-extensions-react/src/surfaces/checkout/components/View/View.ts +++ b/packages/ui-extensions-react/src/surfaces/checkout/components/View/View.ts @@ -1,7 +1,8 @@ import {View as BaseView} from '@shopify/ui-extensions/checkout'; -import {createRemoteReactComponent} from '@remote-ui/react'; +//@ts-ignore +import {createRemoteComponent} from '@remote-dom/react'; import type {ReactPropsFromRemoteComponentType} from '@remote-ui/react'; export type ViewProps = ReactPropsFromRemoteComponentType; -export const View = createRemoteReactComponent(BaseView); +export const View = createRemoteComponent('ui-view', BaseView); diff --git a/packages/ui-extensions-react/src/surfaces/customer-account/components/index.ts b/packages/ui-extensions-react/src/surfaces/customer-account/components/index.ts index 572d77390f..758bd87fea 100644 --- a/packages/ui-extensions-react/src/surfaces/customer-account/components/index.ts +++ b/packages/ui-extensions-react/src/surfaces/customer-account/components/index.ts @@ -9,3 +9,5 @@ export * from './PolicyModal'; export * from './ResourceItem'; export * from './ImageGroup'; export * from './shared-checkout-components'; + +export default {}; diff --git a/packages/ui-extensions-react/src/surfaces/customer-account/components/shared-checkout-components.ts b/packages/ui-extensions-react/src/surfaces/customer-account/components/shared-checkout-components.ts index eb59b7e2e5..0b637597d6 100644 --- a/packages/ui-extensions-react/src/surfaces/customer-account/components/shared-checkout-components.ts +++ b/packages/ui-extensions-react/src/surfaces/customer-account/components/shared-checkout-components.ts @@ -116,7 +116,6 @@ export { Tooltip, type TooltipProps, View, - type ViewProps, ToggleButton, type ToggleButtonProps, ToggleButtonGroup, diff --git a/packages/ui-extensions-react/src/surfaces/customer-account/render.tsx b/packages/ui-extensions-react/src/surfaces/customer-account/render.tsx index 3615aa27d1..ee81ad6e5a 100644 --- a/packages/ui-extensions-react/src/surfaces/customer-account/render.tsx +++ b/packages/ui-extensions-react/src/surfaces/customer-account/render.tsx @@ -1,6 +1,6 @@ +import '@remote-dom/react/polyfill'; import type {ReactElement, PropsWithChildren} from 'react'; import {Component} from 'react'; -import {render as remoteRender} from '@remote-ui/react'; import {extension} from '@shopify/ui-extensions/customer-account'; import type { ExtensionTargets, @@ -10,6 +10,8 @@ import type { import {ExtensionApiContext} from './context'; +import {createRoot} from 'react-dom/client'; + /** * Registers your React-based UI Extension to run for the selected extension target. * Additionally, this function will automatically provide the extension API as React @@ -26,6 +28,7 @@ import {ExtensionApiContext} from './context'; * which allows you to perform initial asynchronous work like fetching data from your * own backend. */ + export function reactExtension( target: Target, render: ( @@ -41,24 +44,12 @@ export function reactExtension( async (root, api) => { const element = await render(api as ApiForRenderExtension); - await new Promise((resolve, reject) => { - try { - remoteRender( - - {element} - , - root, - () => { - resolve(); - }, - ); - } catch (error) { - // Workaround for https://github.com/Shopify/ui-extensions/issues/325 - // eslint-disable-next-line no-console - console.error(error); - reject(error); - } - }); + createRoot(root).render( + + {element} + , + ); + return; }, ) as any; } @@ -85,6 +76,7 @@ export function render( target: Target, render: (api: ApiForRenderExtension) => ReactElement, ): ExtensionTargets[Target] { + //@ts-ignore return reactExtension(target, render); } diff --git a/packages/ui-extensions/package.json b/packages/ui-extensions/package.json index 85ca86f744..57dce6efda 100644 --- a/packages/ui-extensions/package.json +++ b/packages/ui-extensions/package.json @@ -64,7 +64,8 @@ "sideEffects": false, "dependencies": { "@remote-ui/async-subscription": "^2.1.12", - "@remote-ui/core": "^2.2.4" + "@remote-ui/core": "^2.2.4", + "@remote-dom/core": "^1.5.1" }, "devDependencies": { "@shopify/generate-docs": "0.16.4", diff --git a/packages/ui-extensions/src/surfaces/checkout/components/Button/Button.ts b/packages/ui-extensions/src/surfaces/checkout/components/Button/Button.ts index d097833bd3..6d5a382897 100644 --- a/packages/ui-extensions/src/surfaces/checkout/components/Button/Button.ts +++ b/packages/ui-extensions/src/surfaces/checkout/components/Button/Button.ts @@ -1,4 +1,4 @@ -import {createRemoteComponent} from '@remote-ui/core'; +import {createRemoteElement} from '@remote-dom/core/elements'; import type { Appearance, @@ -85,7 +85,11 @@ export interface ButtonProps onPress?(): void; } -/** - * Buttons are used for actions, such as “Add”, “Continue”, “Pay now”, or “Save”. - */ -export const Button = createRemoteComponent<'Button', ButtonProps>('Button'); +export const Button = createRemoteElement<{}>({ + properties: {to: String, foo: String}, + events: {press: {bubbles: true}}, + slots: ['overlay'], +}); + +// @ts-ignore +customElements.define('ui-button', Button); diff --git a/packages/ui-extensions/src/surfaces/checkout/components/Link/Link.ts b/packages/ui-extensions/src/surfaces/checkout/components/Link/Link.ts index 47db56a128..5763ba2ba1 100644 --- a/packages/ui-extensions/src/surfaces/checkout/components/Link/Link.ts +++ b/packages/ui-extensions/src/surfaces/checkout/components/Link/Link.ts @@ -1,10 +1,9 @@ -import {createRemoteComponent} from '@remote-ui/core'; - import type { Appearance, OverlayActivatorProps, DisclosureActivatorProps, } from '../shared'; +import {createRemoteElement} from '@remote-dom/core/elements'; export interface LinkProps extends OverlayActivatorProps, @@ -48,7 +47,9 @@ export interface LinkProps onPress?(): void; } -/** - * Link makes text interactive so customers can perform an action, such as navigating to another location. - */ -export const Link = createRemoteComponent<'Link', LinkProps>('Link'); +export const Link = createRemoteElement<{}>({ + properties: {to: String}, +}); + +// @ts-ignore +customElements.define('ui-link', Link); diff --git a/packages/ui-extensions/src/surfaces/checkout/components/Modal/Modal.ts b/packages/ui-extensions/src/surfaces/checkout/components/Modal/Modal.ts index c68a0cda21..fb12f57e95 100644 --- a/packages/ui-extensions/src/surfaces/checkout/components/Modal/Modal.ts +++ b/packages/ui-extensions/src/surfaces/checkout/components/Modal/Modal.ts @@ -1,5 +1,5 @@ +import {createRemoteElement} from '@remote-dom/core/elements'; import type {RemoteFragment} from '@remote-ui/core'; -import {createRemoteComponent} from '@remote-ui/core'; export interface ModalProps { /** @@ -57,4 +57,11 @@ export interface ModalProps { * * The library automatically applies the [WAI-ARIA Dialog pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/) to both the activator and the modal content. */ -export const Modal = createRemoteComponent<'Modal', ModalProps>('Modal'); +// export const Modal = createRemoteComponent<'Modal', ModalProps>('Modal'); + +export const Modal = createRemoteElement<{}>({ + properties: {id: String}, +}); + +// @ts-ignore +customElements.define('ui-modal', Modal); diff --git a/packages/ui-extensions/src/surfaces/checkout/components/View/View.ts b/packages/ui-extensions/src/surfaces/checkout/components/View/View.ts index 70737ec334..17ce3d3b67 100644 --- a/packages/ui-extensions/src/surfaces/checkout/components/View/View.ts +++ b/packages/ui-extensions/src/surfaces/checkout/components/View/View.ts @@ -1,4 +1,4 @@ -import {createRemoteComponent} from '@remote-ui/core'; +import {createRemoteElement} from '@remote-dom/core/elements'; import type {MaybeResponsiveConditionalStyle} from '../../style/types'; import type { @@ -234,4 +234,9 @@ export interface ViewProps * “natural” size, so this component can be useful in layout components (like `Grid`, * `BlockStack`, `InlineStack`) that would otherwise stretch their children to fit. */ -export const View = createRemoteComponent<'View', ViewProps>('View'); +export const View = createRemoteElement<{}>({ + properties: {padding: String, border: String}, +}); + +// @ts-ignore +customElements.define('ui-view', View); diff --git a/packages/ui-extensions/src/utilities/registration.ts b/packages/ui-extensions/src/utilities/registration.ts index 2d93d7d073..3f6dc36638 100644 --- a/packages/ui-extensions/src/utilities/registration.ts +++ b/packages/ui-extensions/src/utilities/registration.ts @@ -1,4 +1,4 @@ -import {createRemoteRoot} from '@remote-ui/core'; +import '@remote-dom/core/polyfill'; import type { RenderExtensionConnection, @@ -6,6 +6,21 @@ import type { RenderExtensionWithRemoteRoot, } from '../extension'; +import { + // @ts-ignore + BatchingRemoteConnection, + RemoteFragmentElement, + // @ts-ignore + RemoteMutationObserver, + RemoteRootElement, +} from '@remote-dom/core/elements'; + +console.log('### RemoteRootElement', RemoteRootElement); +// @ts-ignore +customElements.define('remote-root', RemoteRootElement); +// @ts-ignore +customElements.define('remote-fragment', RemoteFragmentElement); + export interface ExtensionRegistrationFunction { ( target: Target, @@ -46,28 +61,31 @@ export function createExtensionRegistrationFunction< return (implementation as any)(...args); } - const [{channel, components}, api] = args as [ - RenderExtensionConnection, - any, - ]; + const [{channel}, api] = args as [RenderExtensionConnection, any]; + + // @ts-ignore + const root = document.createElement('remote-root'); + root.connect(channel); - const root = createRemoteRoot(channel, { - components, - strict: true, - }); + // try { + // const observer = new RemoteMutationObserver(channel); + // observer.observe(root); + // } catch { + // } let renderResult = (implementation as any)(root, api); + console.log('### renderResult test', renderResult); + if ( typeof renderResult === 'object' && renderResult != null && 'then' in renderResult ) { renderResult = await renderResult; + console.log('### renderResult promise', renderResult); } - root.mount(); - return renderResult; } @@ -75,6 +93,5 @@ export function createExtensionRegistrationFunction< return extension as any; }; - return extensionWrapper; } diff --git a/yarn.lock b/yarn.lock index f33e6be4a9..2ffac28be5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1775,6 +1775,28 @@ dependencies: jest-matcher-utils "^27.0.0" +"@remote-dom/core@^1.5.0", "@remote-dom/core@^1.5.1": + version "1.5.1" + resolved "https://registry.yarnpkg.com/@remote-dom/core/-/core-1.5.1.tgz#38d9b6a141b0e01779d03ad8088422bdbbf6bc65" + integrity sha512-W+24z1hyhICanCAptXwEvBMEdf+egvQaj/VM7+D3nQEQlllaQYrbcmKLw2O8ydECG2KA0nPwIVYHfq/jGgcdbQ== + dependencies: + "@remote-dom/polyfill" "^1.4.2" + htm "^3.1.1" + +"@remote-dom/polyfill@^1.4.2": + version "1.4.2" + resolved "https://registry.yarnpkg.com/@remote-dom/polyfill/-/polyfill-1.4.2.tgz#ff690dc4e6e6c74458f16b88246acdf9b738fba3" + integrity sha512-UvQLJ/AUPrQZiv9fxmN7uS3AnCUaO4TP6NxAvZ7WLmkF3HRJWh1M9X/oCVNCLt3eiO+dM84nidUG3vOfbJ1lxg== + +"@remote-dom/react@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@remote-dom/react/-/react-1.2.1.tgz#0b6cba85c23fe0ca42c5bbae59d68fc16311db3f" + integrity sha512-RZO+Wu/YThUBhTAD4Qcrg3aoQdAd4ku43E8aoFFyvk1DOkYnLSI+1lM9mP9lKFagXBELD4ojUbENhxKJaqo23w== + dependencies: + "@remote-dom/core" "^1.5.0" + "@types/react" "^18.0.0" + htm "^3.1.1" + "@remote-ui/async-subscription@^2.1.12", "@remote-ui/async-subscription@^2.1.15": version "2.1.15" resolved "https://registry.npmjs.org/@remote-ui/async-subscription/-/async-subscription-2.1.15.tgz" @@ -2170,6 +2192,13 @@ resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz" integrity sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q== +"@types/react-dom@^18.3.0": + version "18.3.0" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.0.tgz#0cbc818755d87066ab6ca74fbedb2547d74a82b0" + integrity sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg== + dependencies: + "@types/react" "*" + "@types/react-reconciler@>=0.26.0 <0.30.0": version "0.28.8" resolved "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.8.tgz" @@ -2185,6 +2214,14 @@ "@types/prop-types" "*" csstype "^3.0.2" +"@types/react@^18.0.0": + version "18.3.12" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.12.tgz#99419f182ccd69151813b7ee24b792fe08774f60" + integrity sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw== + dependencies: + "@types/prop-types" "*" + csstype "^3.0.2" + "@types/resolve@1.17.1": version "1.17.1" resolved "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz" @@ -4216,6 +4253,11 @@ hosted-git-info@^2.1.4: resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz" integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== +htm@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/htm/-/htm-3.1.1.tgz#49266582be0dc66ed2235d5ea892307cc0c24b78" + integrity sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ== + html-encoding-sniffer@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz" @@ -5984,6 +6026,14 @@ quick-lru@^4.0.1: resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz" integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== +react-dom@^18.3.1: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4" + integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw== + dependencies: + loose-envify "^1.1.0" + scheduler "^0.23.2" + "react-is@^16.12.0 || ^17.0.0 || ^18.0.0", react-is@^17.0.1: version "17.0.2" resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz"