Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: Bring your own Web Components #1231

Closed
MarcusNotheis opened this issue Feb 1, 2021 · 4 comments · Fixed by #1250
Closed

RFC: Bring your own Web Components #1231

MarcusNotheis opened this issue Feb 1, 2021 · 4 comments · Fixed by #1250

Comments

@MarcusNotheis
Copy link
Contributor

MarcusNotheis commented Feb 1, 2021

As of now, the UI5 Web Component wrappers, e.g. the <Button /> component is always importing the UI5 Button Web Component:

import '@ui5/webcomponents/dist/Button';

This makes it impossible to "bring your own button", e.g. in a Widget use case.

Option 1: Lazy Load All Web Components in useIsomorphicLayoutEffect

This option removes the top level import of the Web Component and pushes down the import in an useIsomorphicLayoutEffect hook.

Example:

useIsomorphicLayoutEffect(() => {
  if (!customElements.get(effectiveTagName) && !themeProviderContext.webComponentsProvided) {
    import('@ui5/webcomponents/dist/Button')
  }
}, [themeProviderContext.webComponentsProvided]);

This would only load the Button Web Component when the ui5-button tag name is not defined and the ThemeProvider didn't prohibit the loading of web components.

Pro's

  • easy-to-use API
  • big step towards support of Server-Side-Rendering
  • Non-breaking API change

Con's

  • Network-Overhead: Each Web Component is now forced in an own chunk (~90 request for kitchen sink)
  • Possible Race Condition in web components when all components are loaded in parallel?
  • Feels slower than the old solution: Web Components are loaded while DOM is rendering, not before

Option 2: Create Wrapper-only components

This option will create new components that will only contain the Button React component, but without the Web Component import. The Wrapper will be re-exported through a file that will import the Web Component as well for keeping backwards compatibility.

Example

// src/Button/Button.ts (wrapper only)
import { ButtonDesign } from '@ui5/webcomponents-react/lib/ButtonDesign';
import { withWebComponent, WithWebComponentPropTypes } from '@ui5/webcomponents-react/lib/withWebComponent';
import { FC, ReactNode } from 'react';

const Button: FC<ButtonPropTypes> = withWebComponent<ButtonPropTypes>(
  'ui5-button',
  ['design', 'icon', 'iconSize'],
  ['disabled', 'iconEnd', 'submits'],
  [],
  ['click']
);
export { Button };
// src/Button/index.ts
import '@ui5/webcomponents/dist/Button';
export * from './Button';

This approach would allow the end-user to decide whether he want's to use @ui5/webcomponens-react together with the UI5 Web Components or only as a wrapper and he/she is taking care of importing the required web components by themselves.

Pro's

  • Non-Breaking API Change
  • No network overhead
  • Existing apps wouldn't see any difference (main import is still loading the web components together with the wrapper component)

Con's

  • Custom Components like the AnalyticalTable or DynamicPage would need a codemod so that they are offered in two versions as well - with Web Components or without
  • New entry point, might confuse users?
  • No SSR out of the box (you must import the wrapper only components and make sure to load all web components on the client)
  • How do you know which components are used in e.g. the AnalyticalTable? We would have to maintain a list of used components in our documentation, very error prone?
  • For custom components: duplication of library code?

Option 3: Webpack 5 Module Federation

Webpack 5 offers the ModuleFederationPlugin which allows sharing dependencies over multiple micro front ends and loading shared dependencies in case they are not satisfied yet.

Links

Pro's

  • No change in @ui5/webcomponents-react required
  • Micro Frontends are (in my opinion) an edge case and thus no out-of-the-box library support is needed, setup is only required for those users who really need it

Con's

@codefactor
Copy link

@MarcusNotheis ,
Thanks for the summary here about the various options. From our meeting I think we came up with the following conclusions:

Option 1

  • I think we've eliminated this one due to bad user experience and large latencies

Option 2

  • You have some hesitancy because it feels a bit unnatural
  • One way to think about it -- suppose that you think of the ui5 web component imports as a polyfill, and then suppose some browsers had these web components "natively"; what then would we do?
  • If you were to build a codemod for this, it might get very complicated and then possibly break future plans if in case you decided to refactor something, or create something new that didn't follow some specific patterns that the codemod looked for.
  • You had some idea, though, that involved some tree-shaking configuration and simple implementation that could achieve the desired result
  • In the case of a composite Component (like "AnalyticalTable") the consumer who uses the wrapper without any imports might need to break a knowledge barrier in order to use it -- however, I think that we can come up with a good solution to this -- let's talk about it more later if we ever get a POC together and then we can see if this can be solved in an example app consumer with as little knowledge barrier breaking as possible.

Option 3

  • This is always going to be an option for some library consumers, and it would not require any code/build changes.
  • Utilizing webpack 5 federated modules does not conflict with option 2
  • SuccessFactors widgets need to be loaded on legacy JSPs, JUIC pages, and UI5 pages - none of which would be able to use Webpack 5
  • We might try this out and see how far it goes.

In conclusion:

  • SuccessFactors can try out some experiments on option 3, I need to see if multiple widgets that use federated modules load on a legacy page - can they still share?
  • You can try out some simple implementation to achieve option 2 without sacrificing future maintenance

@MarcusNotheis
Copy link
Contributor Author

Hey @codefactor,

I've prepared a pull request for Option 2 and can release a snapshot version for testing.
Currently all Web Components imports are stripped away, but icons are still included.
Do you want that the Icons (e.g. "add") are part of the bundle or do you want to load them externally as well?

@codefactor
Copy link

codefactor commented Feb 5, 2021

@MarcusNotheis ,

I think we would want to, but after thinking the coupling between the tree of imports to the consumer is going to get even worse with icons, by a lot in some cases.

Can we also generate some kind of helper?

Here is some pseudocode for consumer:

// substitute "<path>" with the custom bring your own component directory
import { useAnalyticalTableDependencies } from "<path>/AnalyticalTableDependencies";
import { AnalyticalTable } from "<path>/AnalyticalTable";

export function MyComponent() {
  useAnalyticalTableDependencies();
  return (<AnalyticalTable />);
}

Here is some psuedocode for the dependencies file.

// AnalyticalTableDependencies.js (autogenerated file)
import useHook from somewhere; // not sure best hook here.
export function useAnalyticalTableDependencies() {
  useHook(async () => {
    const promises = [];
    // embeds a tree of all imports that are required here
    if ( tag is not defined ) promises.push(require(pathToTag));
    if ( icon is not defined ) promises.push(require(pathToIcon));
  });
}

Another option -- maybe we have a helper to return a list of tags/icons:

import DEPENDENCIES from "<path>/AnalayticalTableDependencies";
import { useDependencies } from "<path>/DependencyHelper";

export function MyComponent() {
  useDependencies(DEPENDENCIES);
  return (<AnalyticalTable />);
}

@MarcusNotheis
Copy link
Contributor Author

MarcusNotheis commented Feb 17, 2021

hey @codefactor,

we have just merged the corresponding Pull Request and released it with @ui5/webcomponents-react@0.129.4.
You can then import the Wrapper-Only components from '@ui5/webcomponents-react/wrappers/COMPONENT_NAME', e.g. the Button Wrapper component from '@ui5/webcomponents-react/wrappers/Button'.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants