This repository shows 3 different approaches to create re-usable UI widgets using webpack 5.
To view the examples in TypeScript, view the typescript branch.
In this pattern, a host app renders a widget (i.e. an independent piece of UI) one or more times in a page. The widget can in turn be re-used in other host apps. An example of a widget can be a "share on social media" button for sharing a page.
This example consists of a very simple widget that displays the text "Hello from Widget."
for demonstration purposes.
Additionally, this example shows how the host app and widget can be built using the same front-end framework or different ones. In this example vanilla.js (no framework) and react are used for demonstration purposes.
$ npm install --global yarn
$ yarn install
$ yarn start
Then visit:
- umd script tag: http://localhost:8001/
- npm
- npm (no
react
): http://localhost:8002/ - npm (
react
): http://localhost:8003/
- npm (no
- federated modules
- federated modules (no
react
): http://localhost:8004/ - federated modules (
react
): http://localhost:8005/
- federated modules (no
This approach consists of creating a self-contained library bundle that is available on the global object (i.e. window
) and it's namespaced with a library name (MyWidget
in this case).
<script src="http://localhost:8000/dist/my-widget.js"></script>
<script>
MyWidget.render(document.getElementById("widget-container"));
</script>
- Simple to consume.
- Independent deployments (host app and widget).
- Consumer does not require a bundler (e.g.
webpack
).
- Global namespace pollution.
- Internal dependencies are duplicated (if host app and widget use
react
, thenreact
is downloaded and loaded in memory twice).
This approach consists of publishing and consuming the widget as an npm
package.
The widget npm
package is then bundled using webpack
.
{
"dependencies": {
"my-widget": "1.0.0"
}
}
- Simple to consume.
- Isolated scope.
- Dependency re-usage (if host app and widget use
react
, thenreact
is downloaded and loaded in memory once). - Consumer can use any bundler (e.g.
browserify
,rollup
).
- Coupled deployments (a host app deployment is required whenever the widget is updated).
This example shows how the widget can be consumed without using react
.
import { render } from "my-widget";
render(document.getElementById("widget-container"));
This example shows how the widget can be consumed from a react
application.
import React from "react";
import { render } from "react-dom";
import { MyWidget } from "my-widget";
const App = () => (
<div>
<h1>My App</h1>
<MyWidget />
</div>
);
render(<App />, document.getElementById("app-container"));
This approach consists of publishing and consuming the widget using webpack 5 federated modules. In this approach, only the desired bits (i.e. modules) are downloaded and loaded in memory at runtime.
- Independent deployments (host app and widget).
- Isolated scope.
- Dependency re-usage (if host app and widget use
react
, thenreact
is downloaded and loaded in memory once).
- More complex than the other options.
- Host app and widget require to be bundled using
webpack
5.
Note: This approach can be used to build micro frontend architectures.
This example shows how the widget can be consumed without using react
.
import("my_widget_remote").then(({ render }) => {
render(document.getElementById("widget-container"));
});
This example shows how the widget can be consumed from a react
application.
import React, { lazy, Suspense } from "react";
import { render } from "react-dom";
const MyWidget = lazy(() => import("my_widget_remote"));
const App = () => (
<Suspense fallback="Loading...">
<MyWidget />
</Suspense>
);
render(<App />, document.getElementById("app-container"));