{"id":18600156,"url":"https://github.com/workleap/wl-r2wc","last_synced_at":"2025-08-29T20:31:25.506Z","repository":{"id":261491483,"uuid":"865533486","full_name":"workleap/wl-r2wc","owner":"workleap","description":"React to Web Components (r2wc) is a library that helps creating framework agnostic widgets.","archived":true,"fork":false,"pushed_at":"2025-03-28T23:47:19.000Z","size":830,"stargazers_count":2,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-08-29T00:44:56.435Z","etag":null,"topics":["customelements","react","webcomponents"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/workleap.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-09-30T17:28:15.000Z","updated_at":"2025-05-18T17:40:30.000Z","dependencies_parsed_at":"2024-11-06T21:22:00.576Z","dependency_job_id":"8fffdb8c-4b52-41d1-93b1-893d6a975097","html_url":"https://github.com/workleap/wl-r2wc","commit_stats":{"total_commits":107,"total_committers":4,"mean_commits":26.75,"dds":"0.33644859813084116","last_synced_commit":"3beb7217efb964207daf6d9ed239642d53f69fc0"},"previous_names":["gsoft-inc/wl-r2wc","workleap/wl-r2wc"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/workleap/wl-r2wc","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/workleap%2Fwl-r2wc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/workleap%2Fwl-r2wc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/workleap%2Fwl-r2wc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/workleap%2Fwl-r2wc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/workleap","download_url":"https://codeload.github.com/workleap/wl-r2wc/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/workleap%2Fwl-r2wc/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272758377,"owners_count":24988238,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-29T02:00:10.610Z","response_time":87,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["customelements","react","webcomponents"],"created_at":"2024-11-07T02:03:13.064Z","updated_at":"2025-08-29T20:31:25.189Z","avatar_url":"https://github.com/workleap.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Framework Agnostic Widgets\n\nThe purpose of this template is:\n\n- Creating complex components in React and using them inside React and non-React applications.\n- Release once, available everywhere. So, all the consumer apps get updated automatically.\n\nTo do that, we use [Web Components](https://developer.mozilla.org/en-US/docs/Web/API/Web_components) to define custom html elements, e.g `\u003cwl-movie-details/\u003e`, and we use [createRoot](https://react.dev/reference/react-dom/client/createRoot) and [createPortal](https://react.dev/reference/react-dom/createPortal) to fully separate widgets rendering from the hosted app rendering. Then we deploy the scripts on a CDN to allow all consumers to get the same version.\n\nFor example a simple HTML app can use the following `MovieDetails` React component inside their pages as regular HTML tags:\n\n```tsx\nfunction MovieDetails({ mode, showRanking }: MovieDetailsProps) {\n    return \u003cContent\u003e...\u003c/Content\u003e;\n}\n```\n\n```html\n\u003chead\u003e\n    \u003clink rel=\"modulepreload\" href=\"https://cdn.platform.workleap-dev.com/movie-widgets/index.js\" /\u003e\n    \u003cscript type=\"module\" blocking=\"render\"\u003e\n        import { MovieWidgets } from \"https://cdn.platform.workleap-dev.com/movie-widgets/index.js\";\n\n        MovieWidgets.initialize();\n    \u003c/script\u003e    \n\u003c/head\u003e\n\u003cbody\u003e\n    \u003cdiv\u003e\n        \u003cdiv\u003eSelected Movie Details:\u003c/div\u003e\n        \u003cwl-movie-details mode=\"modal\" show-ranking=\"true\" /\u003e\n        \u003cwl-movie-finder /\u003e\n    \u003c/div\u003e\n\u003c/body\u003e\n```\n\n## Code structure\n\n- The `packages/r2wc` package contains the core functionality that will be used by different widgets. \n- The `packages/movie-widgets` package is a sample template to show how to build  framework-agnostic widgets.  \n- The `apps` folder contains a few examples to test `packages/movie-widgets` across different frameworks.\n\n## How to use this repo?\n\nThis repo is a package + template repo which also has some examples to see how this strategy works in action. After cloning the repo:\n\n- Run `pnpm install \u0026\u0026 pnpm build` inside the root,\n- To run the sample apps run `pnpm dev` inside the [Vanilla-Js app](/samples/vanilla-js/) or the [React app](/samples/react/) folders.\n\n\u003e [!IMPORTANT]\n\u003e Whenever you make a change inside the `packages/movie-widgets` you only need to run `pnpm build` and then refresh your running apps. It is becuase of having [dist](/packages/movie-widgets/dist/) folder as `cdn` for each app: [VanillaJs](/samples/vanilla-js/server.js) and [React](/samples/react/webpack.dev.js).\n\nYou can follow the next steps to see how you can change and see the result.\n\n## How to create a framework-agnostic widget?\nIt is recommended having a separate package for this purpose. It helps managing different builds easily. For the rest, `packages/movie-widgets` showcases this.  \n\n### Main logic in React\n\nJust build your regular React components and put them in the `src` folder.\n\nYou are free to create any kind of React component, just there are some rules for  components that are being **exposed** as Web Components:\n\n- They should **NOT** have `children`. e.g.:\n\n  ```tsx\n  function NotAllowedWebComponent({ children }: { children: ReactNode }) {\n      return \u003cdiv\u003e{children}\u003c/div\u003e;\n  }\n  ```\n\n- `JSX` props are **NOT** not allowed as they cannot be translated easily to the similar HTML properties. e.g.:\n  ```tsx\n  function NotAllowedWebComponent({ header }: { header: ReactNode }) {\n      return \u003cdiv\u003e{header}\u003c/div\u003e;\n  }\n  ```\n\n\u003e [!NOTE]\n\u003e The above constraints are ONLY for components that are getting exposed as web components. Any inner components can be implemented as usual.\n\nHere is a valid component which we can later make a web component based on it. Note that `\u003cLayout/\u003e`, `\u003cHeader/\u003e`, `\u003cContent/\u003e` are inner components.\n\n```tsx\n// src/MovieDetails.tsx\nexport interface MovieDetailsProps {\n    showRanking: boolean;\n    onBuy: (movie : MovieData, count: number) =\u003e void;\n    mode: \"modal\" | \"inline\";\n}\n\nexport function MovieDetails({ showRanking, mode, onBuy }: MovieDetailsProps) {\n    const { selectedMovie } = useAppContext();\n\n    return (\n      \u003cLayout mode={mode}\u003e\n        \u003cHeader /\u003e\n        \u003cContent\u003e\n          {movie.details}\n        \u003c/Content\u003e\n        \u003cButton onClick={()=\u003e onBuy(selectedMovie, 1);}\u003e\n      \u003c/Layout\u003e\n    );\n}\n```\n\n#### [Optional] Sharing context\n\nWidgets inside the same project could share context as a regular React app. This context will be used at the rendering step. All widgets are getting rendered inside this context provider (check the [widgets.ts](widgets/src/web-components/widgets.ts) file). For example:\n\n\n```tsx\n// src/WidgetsContextProvider.tsx\nexport function WidgetsContextProvider({ children }: {\n    children?: React.ReactNode | undefined;\n}) {\n    const [isResultOpen, setIsResultOpen] = useState(false);  \n\n    return (\n      \u003cAppContext.Provider value={{ isResultOpen, setIsResultOpen }}\u003e\n          {children}\n      \u003c/AppContext.Provider\u003e\n    );\n}\n```\n\n\u003e [!CAUTION] \n\u003e This context will be used at app level. If you are using client routers like [React Router](https://reactrouter.com/), this context stay live between client page navigations. By client page navigation, we mean the full refresh doesn't happen. \n\u003e\n\u003e It is a great tool if you want to keep an state between page navigations (e.g. keeping the chatbox open). \n\n\u003e [!WARNING]\n The above element is NOT being associated with any DOM element, and the `{children}` (i.e widgets) are being rendered in different DOM nodes (Thanks to React [createPortal](https://react.dev/reference/react-dom/createPortal)). So, if any of above providers generate DOM element, they are not being present in DOM hierarchy.   \n\n#### [Optional] Widgets settings\n\nThere are scenarios where you want to pass down some high level settings that are being used by all widgets. For example:\n\n- Passing app current theme or language\n- Passing backend API URL, app name, app logo, etc.\n\nIf you have only one widget, it is ok to pass them through it as widget props, but if you have multiple widgets, it is not perfect to do the same for all widgets. To do that, add these settings to `WidgetsSettings` and modify previously created `WidgetsContextProvider` to handle them.\n\n```tsx\n// src/WidgetsContextProvider.tsx\nexport interface WidgetsSettings {\n    theme: \"light\" | \"dark\" | \"system\";\n    language: string;\n}\n\nexport function WidgetsContextProvider({ children, ...props }: PropsWithChildren\u003cWidgetsSettings\u003e) {\n    const [theme, setTheme] = useState(props.theme);\n\n    useEffect(() =\u003e {\n        setTheme(props.theme);\n    }, [props.theme]);\n\n    return (\n        \u003cI18nProvider lang={props.language}\u003e\n            \u003cThemeProvider theme={theme}\u003e\n                {children}\n            \u003c/ThemeProvider\u003e\n        \u003c/I18nProvider\u003e\n    );\n}\n```\nPay attention to the `useEffect` in the above code. We need it if we wrap a setting with `useSate`. In this case, the passed value to `useState` is only for initiation and it is not getting updated on later calls. As the host app can change the widgets settings through the `update` method, we need to use `useEffect` to make sure the state gets the changes. \n\n\u003e [!NOTE]\nYou need to merge the two above examples if you support both optional \"Sharing context\" and passing down \"Widgets Settings\" use cases.\n\n\n### Create Web Components\n\nIn this section we create [custom elements](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements) (part of Web Components) to expose our React components as framework agnostic widgets. We don't need to create custom elements for inner components (e.g. `Body` , `Item`, `Header`)\n\nTo make life easier, we moved the generic codes to the `@workleap/r2wc` package (React to Web Component).\n\n\n#### Define custom elements\n\nTo create a custom element, \n- inherit from the `WebComponentHTMLElement\u003cProps, ObservedAttributesType\u003e` class (inside `@workleap/r2wc` package), where:\n- (optional) `Props` is the React component `Props` type. \n- (optional) `ObservedAttributesType` is the union type for new observed HTML attributes. This helps to keep type safety for them. You will see the usage in the following example. \n\nDefine these properties in the inherited class:\n- `tagName` (**required**): To set the HTML attribute name.\n- `reactComponent` (**required**): to set the related React component,\n- `observedAttributes`: to set the newly added HTML attributes if there is any. \n- `map`: to define a map for HTML attributes to React props, and HTML events to React callback props.\n- `initialStyle`: to define initial style for the Web Component before having it rendered. it is really helpful to prevent [layout shifting](https://web.dev/articles/cls) while waiting the web component to get rendered. \n\nIt is also required to define a `ObservedAttributesType`  union type based on the `observedAttributes` values.\n\n```tsx\n// src/MovieDetailsElement.tsx\nimport { WebComponentHTMLElement, type Map } from \"@workleap/r2wc\"\n\ntype ObservedAttributesType = (typeof MovieDetailsElement.observedAttributes)[number];\n\nexport class MovieDetailsElement extends WebComponentHTMLElement\u003cMovieDetailsProps, ObservedAttributesType\u003e {\n    static tagName = \"wl-movie-details\";\n    static observedAttributes = [\"show-ranking\", \"mode\"] as const;\n\n    get reactComponent() {\n        return MovieDetails;\n    }\n\n    get map(): Map\u003cMovieDetailsProps, ObservedAttributesType\u003e {\n        return {\n            attributes: {\n                \"show-ranking\": { to: \"showRanking\", convert: value =\u003e value === \"true\" },\n                \"mode\": \"mode\"\n            },\n            events: {\n                \"on-buy\": \"onBuy\"\n            }\n        };\n    }\n\n    get initialStyle(): Partial\u003cCSSStyleDeclaration\u003e {\n        return {\n            display: \"block\",\n            height: \"48px\"\n        };\n    }    \n}\n```\n\n\u003e [!NOTE]\n\u003e **`static observedAttributes`**\n\u003e - Attribute names should follow Kebab-Case naming convention. \n\u003e - Follow the array with `as const`  to get type-safety support.\n\u003e\n\u003e \u0026nbsp;\n\u003e\n\u003e **`get map()`**\n\u003e \n\u003e It has two parts: `attributes` and `events`. \n\u003e \n\u003e - **`attributes`**\n\u003e \n\u003e   The left side of each map item is attribute name, and the right side says how to map from attribute to the related React prop. You have to define a map for all attributes defined in `observedAttributes` array.\n\u003e   \n\u003e   The right side of the map could be:\n\u003e   - The React property name defined in `Props`, or\n\u003e   - An object that also defines how to convert the passed HTML attribute value from string. `{to: 'propName', convert: (value:string)=\u003e PropertyType}`\n\u003e\n\u003e \u0026nbsp;\n\u003e \n\u003e - **`events`**\n\u003e \n\u003e   This section defines new HTML events for related callbacks in `Props`. Note that unlike `observedAttributes`, we don't need to define these event names separately.  \n \n\u003e [!TIP]\n\u003e The base class has the `data` property ([not attribute](https://open-wc.org/guides/knowledge/attributes-and-properties/)) for the underlying React component. In other words, you can use the `data` property in Javascript to get and set all React props regardless of declaring attributes or events. \n\nPut the created file inside the `src` folder.\n\n\n#### Define WidgetsManager\n\nThe host app needs an API to register and initialize the widgets. `WidgetsManager` class does this for you. To do that, create the [widgets.ts](/packages/movie-widgets/src/widgets.ts) file and create a new instance of the `WidgetsManager` class. Its construcor accepts this parameters:\n\n- `elements` (**required**): An array of widgets to register. Without having them registered, you cannot use them in the host app.\n- `contextProvider`: to pass shared context provider, e.g. `WidgetsContextProvider`.\n- `contextProviderProps`: to pass initial context provider props. It is helpful when you want to initiate some props through build time, and not leaving it to `initialize` function. For example, when you want to inject event emitter and expose the function through the `extend` method. One popular example could be any type of refresh data methods.\n- `ignoreLoadingCss`: if you want the host to load the related CSS file, set this to true. Otherwise the manager will load the css file [automatically](/packages/r2wc/src/WidgetsManager.tsx).\n- `syncRendering`: (**not recommended**) If you want the web component rendering happens syncronously. It may be useful for critical widgets that need to be present as soon as the page gets loaded. It is not recommended as it uses [flushSync](https://react.dev/reference/react-dom/flushSync) behind the hood and it may affect the overal page load time.  \n\nThen set the result to a const and export it from the module. \n\nIf you like to have access to it across the whole document, you can assign it to a window variable.\n\n```tsx\n// src/widgets.ts\nconst MovieWidgets = new WidgetsManager({\n    elements: [MovieDetailsElement, MoviePopUpElement],\n    contextProvider: WidgetsContextProvider\n});\n\nwindow.MovieWidgets = MovieWidgets;\n\nexport { MovieWidgets };\n```\n\n\u003e [!IMPORTANT]\n\u003e You need to create a `types.d.ts` [file](/packages/movie-widgets/src/types.d.ts) and add the following declaration to be able to define `window.MovieWidgets` at window level:\n\u003e ```ts\n\u003e import type { IWidgetsManager } from \"@workleap/r2wc\";\n\u003e import type { WidgetsSettings } from \"./WidgetsContextProvider.tsx\";\n\u003e \n\u003e export declare global {\n\u003e     interface Window {\n\u003e         MovieWidgets?: IWidgetsManager\u003cWidgetsSettings\u003e;\n\u003e     }\n\u003e }\n\u003e ```\n\nIf you don't have `contextProvider`, simply ignore it:\n```tsx\n// src/widgets.ts\nconst MovieWidgets = new WidgetsManager({\n    elements: [MovieDetailsElement, MoviePopUpElement]\n});\n\nwindow.MovieWidgets = MovieWidgets;\n\nexport { MovieWidgets };\n```\n\n`WidgetsManager` loades the related `CSS` file automatically at the time of load. If you want to load the CSS file manually, you can pass the `ignoreLoadingCss: true` to the constructor.\n\n`WidgetsManager` class exposes the following API which is being used inside the host apps.\n- `initialize(settings: WidgetsSettings)`: To initiate the widgets and pass the initial state of `WidgetsSettings`.\n- `update(settings: Partial\u003cWidgetsSettings\u003e)`: To change the state of `WidgetsSettings`. You only need to pass the changed settings.\n- `settings: WidgetsSettings`: To get the current widgets settings.\n- `unmount()`: To unmount the rendered elements. You can call `initialize` after to get a fresh rendering. It is mostly helpful in test environments (like Storybook) where you need to re-initialize the widgets without reloading the whole page. Note that this function doesn't remove the widgest tags from the page. It only removes the rendered content.\n\n#### Extending WidgetsManager\nTo add custom functionalities to `WidgetsManager` you can simply use the `extends\u003cT\u003e(data: T)` function. All the passed data will be injected to the instanciated `WidgetsManager` and will be exposed through provided variable. In the following example, `window.MovieWidgets.refreshData()` is a valid and type-safe method. \n```ts\nexport declare global {\n    interface Extensions {\n        refreshData: ()=\u003e void;\n    }    \n    interface Window {\n        MovieWidgets?: IWidgetsManager\u003cWidgetsSettings\u003e \u0026 Extensions;\n    }\n }\n\nconst refrehHandler = new InvokeMethodHandler();\n\nconst MovieWidgets = new WidgetsManager({\n    elements: [MovieDetailsElement, MoviePopUpElement],\n    contextProvider: WidgetsContextProvider,\n    contextProviderProps: {\n        refreshHandler: refrehHandler\n    }\n}).extends({\n    refreshData : refrehHandler.emit.bind(refrehHandler)\n});\n\nwindow.MovieWidgets = MovieWidgets;\n\nexport { MovieWidgets };\n```\n\n\n#### [Optional] Define React helpers\nIf you want to ease the process of using your defined web components inside React hosts, you\nhave to create the following files and [later](#optional-react-helpers-output) set up the package to export them properly.\n\nFirst, declare types for defined Web Components inside the [/src/helpers/react/types.d.ts](/packages/movie-widgets/src/helpers/react/types.d.ts) file:\n\n```ts\nimport type { WebComponentHTMLElement } from \"@workleap/r2wc\";\n\nexport declare global {\n    // eslint-disable-next-line @typescript-eslint/no-namespace\n    namespace JSX {\n        interface IntrinsicElements {\n            \"wl-movie-details\": WebComponentHTMLElement;\n            \"wl-movie-finder\": WebComponentHTMLElement;\n        }\n    }\n}\n```\n\nSecond, define wrapper components inside [src/helpers/react/index.ts](/packages/movie-widgets/src/helpers/react/index.ts) to ease the usage. Make sure you export both `types.d.ts` file as mentioned below:\n\n```ts\nimport { createWebComponent } from \"@workleap/r2wc/helpers/react\";\nimport type { MovieDetailsProps } from \"../../MovieDetails.tsx\";\n\nexport type * from \"../../types.d.ts\";\nexport type * from \"./types.d.ts\";\n\nexport const MovieDetails = createWebComponent\u003cMovieDetailsProps\u003e(\"wl-movie-details\");\nexport const MovieFinder = createWebComponent(\"wl-movie-finder\");\n```\n\n\u003e [!TIP]\n\u003e With above definitions, the host app can easily use the component's props similar to how they defined, but just through the `data` property:\n\u003e ```ts\n\u003e function Page() {\n\u003e   return (\n\u003e     \u003cMovideDetails data={{mode: \"inline\", showRanking: true}} /\u003e\n\u003e   );\n\u003e }\n\u003e ```\n\u003e Without these helpers, consumers have to do more to be able to use Web Components.\n\n\n## Build the output\nEverything is ready! We setup two different builds. One for CDN output, and the other a helper package for React hosts.\n\n### CDN output\n\n Just make sure you have setup the [tsup.build.cdn.ts file](/packages/movie-widgets/tsup.build.cdn.ts) correctly:\n\n```ts\nexport default defineBuildConfig({\n    entry: [\"src/index.ts\"],\n    outDir: \"dist/cdn\",\n    noExternal: [/.*/],\n    dts: false,\n    minify: true,\n    treeshake: true\n});\n```\n\nAfter running the `pnpm build` inside the package, you will get `index.ts` and `index.css` files inside the `dist/cdn` folder. You need them in host apps to load and render widgets.\n\n\u003e [!IMPORTANT]\n\u003e This output is **NOT** for packaging. It is expected to be downloaded by the host apps from a CDN.\n\n\n### [Optional] React helpers output\nIf you have defined [React helpers](#optional-define-react-helpers), you have to build the special output of your package as weel. \n\n First, create the [tsup.build.react.ts file](/packages/movie-widgets/tsup.build.react.ts). Rememer to enable `dts: true`: \n```ts\nexport default defineBuildConfig({\n    entry: [\"src/helpers/react/index.ts\"],\n    outDir: \"dist/react\",\n    dts: true,\n    clean: true,\n    sourcemap: true\n});\n```\n\nSecond, add the following to the [package.json](/packages/movie-widgets/package.json):\n```json\n    \"name\": \"@samples/movie-widgets\",\n    \"version\": \"1.0.0\",\n    \"main\": \"dist/react/index.js\",\n    \"types\": \"dist/react/index.d.ts\",\n    \"exports\": {\n        \"./react\": {\n            \"import\": \"./dist/react/index.js\",\n            \"types\": \"./dist/react/index.d.ts\"\n        }\n    },\n    \"files\": [\n        \"dist\"\n    ],\n```\nThe above ensures you can import `@samples/movie-widgets/helpers/react` inside the React hosts.\n\n## Deployment\n\nThe generated files inside the `/dist/cdn` folder (i.e `index.js` and `index.css`) need to be made available to our widgets consumers.\n\n\u003e [!CAUTION]\n\u003e #### The total combined size of these two files (after minification and Gzip compression) **SHOULD NOT** exceed 100KB.\n\u003e \n\u003e You can use [this extension](https://marketplace.visualstudio.com/items?itemName=mkxml.vscode-filesize) to see the Gzip size of the generated files.\n\nThese files need to be hosted in an Azure storage container, served via Azure Front Door. The storage system automatically handles compression in Brotli (`.br`) or Gzip (`.gz`) formats. Make sure to activate the compression in the optimization settings of the Front Door profile. That storage will need to be set up by the team that owns the widgets project.\n\n### How to deploy the changes on a CDN?\n\n#### Manual Upload\n\nCurrently, the deployment process involves manually uploading the index.js and index.css files to the Azure storage container.\n\n- Navigate to the Azure portal and locate the appropriate storage account and container.\n- Upload the files to the container. Make sure to replace any existing files.\n- The CDN will automatically serve the updated versions with appropriate compression.\n\n#### Automating the Deployment (Future):\n\nThis proof of concept does not currently have an automated deployment process. However, the team that owns the widgets project could set up a CI/CD pipeline to automatically deploy the changes to the Azure storage container.\n\n#### URLs:\n\nAfter deployment, the files will be available on a public URL:\n\nFor instance, for this template, the URLs are:\n\n- JavaScript: https://cdn.workleap.com/movie-widgets/index.css\n- CSS: https://cdn.workleap.com/movie-widgets/index.css\n\n#### Versioning\n\nVersioning is not required in the default setup since we aim to avoid breaking changes. By avoiding breaking changes, all consumers can continue using the latest version of the files without needing to update their applications.\n\n- You should prioritize deprecating old features instead of removing them,\n- You should use feature flags to opt-in to new features that would otherwise be breaking changes.\n\nHowever, in the event that breaking changes need to be introduced, versioned folders can be added to the CDN.\n\nFor example, breaking changes might be deployed to:\n\n- JavaScript: https://cdn.workleap.com/movie-widgets/2/index.js\n- CSS: https://cdn.workleap.com/movie-widgets/2/index.css\n\nIn such cases, consumers will need to manually update the URLs in their applications to point to the new version (/2/index.js and /2/index.css). Since breaking changes are involved, this manual update is necessary to ensure compatibility with the new version.\n\n## Usage\n**How to consume framework agnostic widget?**\n\nThe framework agnostic widget can be consumed directly in any HTML page by referencing the deployed CDN files. To include the widgets in your project, use the following snippet:\n\n```html\n\u003clink rel=\"modulepreload\" href=\"https://cdn.workleap.com/movie-widgets/index.js\" /\u003e\n\u003cscript type=\"module\" src=\"https://cdn.platform.workleap-dev.com/movie-widgets/index.js\"\u003e\u003c/script\u003e\n```\n\n\u003e [!IMPORTANT]\n\u003e The `modulepreload` is almost enough but to get better perforamnce, add the `script` tag right after to run it immediately. This helps on faster custom element registeration. Without it, the script will run when the first usage comes up. \n\n\u003e [!TIP]\n\u003e The `css` file is being loaded automatically through the above script if you haven't disabled it. \n\nOnce this is added to the HTML page, the script can now inject the new Web Components into the page. This can be done through calling  `initialize` method.\n\n### Vanilla Js \n\nAn example usage of the widget in an HTML page:\n\n```html\n\u003chtml lang=\"en\"\u003e\n    \u003chead\u003e\n        \u003clink rel=\"modulepreload\" href=\"/cdn/movie-widgets/index.js\" /\u003e\n        \u003cscript type=\"module\" src=\"https://cdn.platform.workleap-dev.com/movie-widgets/index.js\"\u003e\u003c/script\u003e\n        \u003cscript type=\"module\"\u003e\n            import { MovieWidgets } from \"/cdn/movie-widgets/index.js\";\n\n            MovieWidgets.initialize({ theme: \"light\" });\n\n            const movieDetails = document.getElementByTagName(\"movie-details\")\n            movieDetails.addEventListener(\"on-buy\", function (movie, count) {\n                alert(`bought ${count} tickets of ${movie.title}`);\n            });        \n        \u003c/script\u003e\n    \u003c/head\u003e\n    \u003cbody\u003e\n        \u003cwl-movie-details mode=\"inline\" show-ranking=\"true\" /\u003e\n        \u003cwl-movie-finder /\u003e\n    \u003c/body\u003e\n\u003c/html\u003e\n```\n\n### React + Typescript\n\nif you have defined [React helpers](#optional-define-react-helpers), you can easily import `@samples/movie-widgets` package and then use the generated Web Components wrappers:\n\n```jsx\nimport { MovieDetails, MovieFinder } from \"@samples/movie-widgets/react\";\n\n\u003cMovieDetails data={{ mode: \"modal\", showRanking: true, onBuy: buyTickets }} /\u003e\n\u003cMovieDetails data={{ showRanking: false, mode: \"inline\" }} /\u003e\n\u003cMovieFinder style={{ fontWeight: \"bold\" }} /\u003e\n```\n\n\u003e [!NOTE]\n\u003e You can use regular HTML attributes, like `style`, with these components.\n\n#### Initial script\nThis part is pretty similar to VanillaJS example. As we load this package from CDN, **NOT** as a package, we have to load it separately in `index.html` file:\n\n```html\n\u003chtml lang=\"en\"\u003e\n    \u003chead\u003e\n        \u003clink rel=\"modulepreload\" href=\"/cdn/movie-widgets/index.js\" /\u003e\n        \u003cscript type=\"module\" src=\"https://cdn.platform.workleap-dev.com/movie-widgets/index.js\"\u003e\u003c/script\u003e\n        \u003cscript type=\"module\"\u003e\n            import { MovieWidgets } from \"/cdn/movie-widgets/index.js\";\n            MovieWidgets.initialize({ theme: \"light\" });\n        \u003c/script\u003e\n    \u003c/head\u003e\n    \u003cbody\u003e\n        \u003cdiv id=\"root\"\u003e\u003c/div\u003e\n    \u003c/body\u003e\n\u003c/html\u003e\n```\n\n\n## Future Improvements\n\nEven if the current POC is working, there are some improvements that we will look at in the future:\n\n- Possibility of implementing the widget using the Shadow DOM to avoid conflicts with the host app styles.\n  - [ ] Pushing all elements to render inside Shadow root. Currently Orbiter renders Modal and Menu at document level which causes them to get their styles from the main document, not the shadow root styles.\n- Strategy to load some components dynamically to decrease the whole package size\n- [Use SSR + Declarative Shadow Dom](https://web.dev/articles/declarative-shadow-dom) to boost performance and remove flickering at all\n\n\n\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fworkleap%2Fwl-r2wc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fworkleap%2Fwl-r2wc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fworkleap%2Fwl-r2wc/lists"}