creating pure web component from react components - reactjs

i am trying to build web components from react components, it is all working fine, but there are two problems i am trying to solve:
Is there a way to convert such web components to pure web component (using webpack, transpile or some other way), so that react and other dependencies are not bundled?
Is there a way to just include the required portion of dependencies or it is all/none only, and have to use external setting of webpack to use host's version?
thanks

For the first question, there is no direct way to convert React component into a Web Component. You will have to wrap it into a Web Component Class:
export function MyReactComponent() {
return (
<div>
Hello world
</div>
);
}
class MyWebComponent extends HTMLElement {
constructor() {
super();
// Do something more
}
connectedCallback() {
// Create a ShadowDOM
const root = this.attachShadow({ mode: 'open' });
// Create a mount element
const mountPoint = document.createElement('div');
root.appendChild(mountPoint);
// You can directly use shadow root as a mount point
ReactDOM.render(<MyReactComponent />, mountPoint);
}
}
customElements.define('my-web-component', MyWebComponent);
Of course, you can generalize this and create a reusable function as:
function register(MyReactComponent, name) {
const WebComponent = class extends HTMLElement {
constructor() {
super();
// Do something more
}
connectedCallback() {
// Create a ShadowDOM
const root = this.attachShadow({ mode: 'open' });
// Create a mount element
const mountPoint = document.createElement('div');
root.appendChild(mountPoint);
// You can directly use shadow root as a mount point
ReactDOM.render(<MyReactComponent />, mountPoint);
}
}
customElements.define(name, MyWebComponent);
}
register(MyReactComponent, 'my-web-component');
Same register function can be now re-used across all the components that you want to expose as web components. Further, if your component accepts props that should be passed, then this function can be changed to accept third argument as array of string where each value would be registered as a setter for this component using Object.define. Each time a setter is called, you can simply call ReactDOM.render again.
Now for the second question, there are multiple scenarios with what you are trying to do.
If you are bundling application and loading dependencies like React or others using CDN, then Webpack externals is a way to go. Here you will teach Webpack how to replace import or require from the global environment where app will run.
If you are bundling a library which you intend to publish to NPM registry for others to use in their applications, then you must build your project using library target configuration. Read more about authoring libraries here.
Bundling for libraries is slightly trickier as you will have to decide what will be your output format (common.js, ES Modules or UMD or Global or multiple formats). The ideal format is ES Module if you are bundling for browser as it allows better tree shaking. Webpack previously did not support Module format and has recently started supporting it. In general, I recommend Webpack for applications and Rollup.js for libraries.

If you're looking to do this manually, the answer from Harshal Patil is the way to go. I also wanted to point out a library that I helped create, react-to-webcomponent. It simplifies this process and seamlessly supports React 16-18.

React is not a library made to build native web-components.
Writing web-components by hand is not the best option neither as it won't handle conditions, loops, state change, virtual dom and other basic functionalities out of the box.
React, Vue Svelte and other custom libraries certainly have some pros while native web-components have many other advantages like simplicity of ecosystem and being ported by browsers and W3C.
Some libraries that will help you write native web-components in a modern and handy way:
Lego that is alightweight, native and full-featured in a Vue style.
Nativeweb lightweight and raw web-components
ElemX a proof-of-concept that binds native web-component to ElemX functionalities.
If you really wanted to wrap a React component into a native web component, you could do something like:
class MyComponent extends HTMLElement {
constructor() {
this.innerHTML = '<MyReactComponent />'
}
}
customElements.define('my-component', MyComponent)
And use it in your HTML page <my-component />

You can use remount library
/** Main.jsx */
import React from 'react'
import App from './App'
import { define } from 'remount';
define({'react-counter': App},
{
attributes: ['defaultValue'],
shadow:false
})
/** App.jsx */
import React from "react";
import Counter from "./components/Counter.jsx";
function App({defaultValue}) {
return (
<div>
<h1>Counter Component</h1>
<Counter defaultValue={defaultValue}/>
</div>
)
}
export default App;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
</head>
<body>
<react-counter defaultValue="5"></react-counter>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

Related

Dynamically load React component library from URL?

I am working on documentation tool for Typescript library. The idea is to leverage parcel's watch mode to continuously build the library, and use the same in a pre-built documentation app.
For the same I need to load a module library (built in another project) dynamically via URL.
<script type="module">
const libraryModule = "http://localhost:8080/lib.module.js";
const promise = import(libraryModule);
promise.then(library => {
// do something with library
window.ComponentLibrary = library;
});
</script>
However, parcel replaces the above import with require and the load fails. Using System.import throws System is not defined error.
I tried to use dynamic-import-polyfill and then initialize it as under and the use as below:
dynamicImportPolyfill.initialize({
modulePath: 'http://localhost:13090', // Defaults to '.'
importFunctionName: '$$import' // Defaults to '__import__'
const promise = $$import(libPath);
});
This throws the following error:
TypeError: Failed to resolve module specifier "react/jsx-dev-runtime". Relative references must start with either "/", "./", or "../"
I have also tried using script type as text/javascript but doesn't work either.
Looking for guidance on the best way here to get the component library loaded?
Figured it out: yes, we can load a component library as a module dynamically.
The issue was that React UMD module is not a pure ES/Javascript module. Also, with React 17, JSX components are picked from react/jsx-runtime. So, first I had to convert the React UMD module into an ES module - it's just a thin wrapper. Similarly, added a wrapper for jsx-runtime. To make things work had to use importmaps which are currently not supported in all browsers - see caniuse.com to check latest support.
This completes your setup and now your library compiled as ES module will work just fine. Below is what I used to get working:
<script type="importmap">
{
"imports": {
"react/jsx-runtime": "/react-jsx-runtime.js",
"react": "/react-as-es-module.js"
}
}
</script>
<script type="module" src="/component-library.js"></script>
<script type="module">
import * as MyComponentLib from "/component-library.js";
window.ComponentLibrary = { ...MyComponentLib };
</script>
Code for react-jsx-runtime.js looks as under:
import * as React from 'react';
export const jsx = React.createElement;
export const jsxs = React.createElement;
Code for react-as-es-module.js goes as:
import 'https://unpkg.com/react#17.0.2/umd/react.production.min.js';
const {
Children,
Component,
Fragment,
// and all other exports
} = React || {};
export {
Children,
Component,
Fragment,
// and all other exports
}
export default React;
I compiled component-library.js using ParcelJS using the type: "module" in package.json file. I would detail this in blog post and demo Github repo soon.
Hope this helps.

Full-fledged PDF.js viewer in React

I'm looking to use the full-featured PDF.js in a React component in a Next.js project, as seen in Firefox and as on this online demo. Some important features here are being able to navigate to a certain page number by typing it in, and searching for text in the PDF. Is there a React component available for that?
The library react-pdf is nice for rendering a single page, but doesn't provide a toolbar or a convenient way of lazily loading pages in a scrollable view.
Similar to the questions How to use full PDF.js viewer with toolbar in webpack and Vuejs? (where the accepted answer provides a Vue component) and Embed Full Mozilla pdf.js viewer in vue.js ( using webpack via vue-cli ), but for React.js.
I tried including including /web/viewer.html as part of the inner HTML of a React component by doing the following, but it didn't work out.
Download the latest release and extract it to a folder part of my Next.js project (which I called pdfjs). I tried several folders, such as /client, /client/components, /pages, /node_modules, and /.
Run npm install --save-dev html-loader
Use this Webpack loader that parses HTML files, by changing next.config.js to the following:
module.exports = {
// …
webpack: (config, options) => {
config.module.rules.push({
test: /\.html$/,
exclude: /node_modules/,
use: { loader: 'html-loader' }
});
return config;
},
}
Create a simple page under /pages as follows:
import React from 'react';
import PdfViewer from '../pdfjs/web/viewer.html'
export default function () {
return (
<div className="content" dangerouslySetInnerHTML={{ __html: PdfViewer }} />
);
};
After running next in terminal to start a dev server and navigating to that page in the browser, I get an error about the JavaScript heap running out of memory.
Even if my computer had enough memory, I'm not sure that this would actually result in the PDF rendering – not to mention the danger of using dangerouslySetInnerHTML. It looks like a better solution would probably be to have an actual React component rather than trying to embed an HTML file.
I think this might be more of what your after. I wrapped it in a component for you already but this is a document viewer which can view PDF documents with out much work.
import React,{ Component } from 'react';
import ReactDOM from 'react-dom';
class DocView extends React.Component{
constructor(props){
super(props);
}
render(){
var url = "https://docs.google.com/viewerng/viewer?url="+this.props.src+"&embedded=true";
return(
<iframe style={this.props.style} src={url}></iframe>
);
}
}
export default DocView;
CloudPDF offers a React PDF viewer. It is basically pdf.js but then pre-rendered on the server. This gives the possibility for lazy loading of large pdf files and still keeping performance. And by default has a nice layout for the viewer.
import CloudPdfViewer from '#openbook/cloudpdf-viewer';
export default function () {
return (
<CloudPdfViewer documentId="346467a6-fa61-43ad-b45a-d1fdc3da0007" width="100%" height="500px" />
);
};
Disclamer: I am working for CloudPDF and it is still a beta version.

How does React Lazy fetch components?

I recently read about React Lazy and how it "loads" the components during run time when they are required to be rendered. I assume that "loading" here means fetching the component from the server and then rendering it.
So my question is, how does React manage this fetching of components? How does it know the exact path from where to fetch this component (given that our code will mention the relative path but fetching will require complete server path)? Does it depend on Webpack for this?
Let's look into the React code. React.lazy is defined as follows.
export function lazy<T, R>(ctor: () => Thenable<T, R>): LazyComponent<T> {
let lazyType = {
$$typeof: REACT_LAZY_TYPE,
_ctor: ctor,
// React uses these fields to store the result.
_status: -1,
_result: null,
};
if (__DEV__) {
// ... additional code only in development mode
}
return lazyType;
}
As you can see, React.lazy requires a Promise which resolves to a module with a default export containing a React component (freely cited by React Docs). This also means that not React resolves the file, but import() does. import() works as documented in the MDN.
The async import() is a new function in ES6 which is not available in all browsers but can be polyfilled by Webpack and Babel/Typescript/others.
What you often see is code like the following, which automatically splits the imported file away by Webpack.
import(/* webpackChunkName: "xyz" */ './component/XYZ')
This creates a new javascript xyz.js next to your bundle script.
If you don't use Webpack, you need to create those files by yourself. Webpack just reduces the work required from you. So you don't absolutely depend on Webpack. This approach might look like the following:
// ./component/xyz.js
export default function() { return <div>Component</div> }
// ./main.js
const OtherComponent = React.lazy(() => import('./component/xyz.js'));
export default function() { return <div>Component</div> }
And the file structure:
| public
|---| main.js
|---| component
|---| --- | main.js
As you see, no webpack required. It just makes your life easier.

How to write Autodesk.Viewing.Extensions with webpack and react?

I was write code within view3D v2.11, React, ES6 and webpack. But I don't know how to write Autodesk.Viewing.Extensions within webpack and React. Can anyone show me some examples?
Using webpack to write a viewer extension is no different than using webpack to write any other js application. Take a look at my extensions library repo, each extension is bundled into a separate .js or .min.js whether you build the project in dev or prod: library-javascript-viewer-extensions.
This is designed this way so each extension can be loaded dynamically independently, however if you build an entire application around the viewer, you may simply include the code for each extension along with the rest of the app and generate a single webpack bundle.
This React project contains multiple viewer extensions (some are extracted from the library mentioned above) and is bundling extensions code along with the rest of the app: forge-rcdb.
As far as React and the viewer are concerned, it's not very relevant because the viewer is a 3D component which is created dynamically, all WebGL canvas and viewer 2D elements are being generated after your app loads a model, whereas React lets you declare 2D components when the app is starting. I was playing a bit with injecting dynamically React components to the viewer div, you can take a look in that same project here:
this.viewer = new Autodesk.Viewing.Private.GuiViewer3D(this.viewerContainer)
// Experimental !
// This this to render dynamic components
// inserted after the viewer div has been created
const reactViewerNode = document.createElement('div')
this.viewer.container.appendChild(reactViewerNode)
this.viewer.react = {
node: reactViewerNode,
components: [],
addComponent: (component) => {
this.viewer.react.components.push(component)
},
render: (props) => {
ReactDOM.render(
<div>
{
React.Children.map(
this.viewer.react.components,
(child) => React.cloneElement(child, props))
}
</div>,
reactViewerNode)
}
}
I then render those dynamic components by overriding componentDidUpdate:
componentDidUpdate () {
if (this.viewer && this.viewer.react) {
if(this.viewer.react.components.length) {
this.viewer.react.render(this.props)
}
}
}
and there is an example of use:
viewer.react.addComponent(
<DBDropdown key="test" className="react-div">
</DBDropdown>
)
I actually haven't implemented any feature using that in the live version of the app, but it should give you an idea.
Hope that helps.

How to load es6 module into amd architecture

I have a requirejs app and I want to append a react module / component into my page. The react module is written in babel / jsx. So I use the AMD transform plugin AND I concat them into one single file.
Now of course each react js file has export default class / function-part.
One of the react files has a function called startUp(html) which expects a html-element as parameter. This html parameter will be used by ReactDOM.render call.
My problem is that I cannot access the startUp-method in my AMD-module:
////////////////////// REACT AND BABEL / ES6 CODE /////////////////////////
// react-module.js
export default class MyReactContainer extends React.Component {
render() {
return <div>Just a DIV</div>
}
}
// react-main.js
export default function startUp(html) {
ReactDOM.render(<MyReactContainer />, html);
}
//////////////////// EXISTING AMD MODULE /////////////////////////////////
// reactModuleIncluder.js
define(['./react-main'], function(reactMain) {
function includeReactModule(html) {
reactMain(html); // Here I want to call the startUp method
}
return includeReactModule;
});
Any idea what I can do? Or is this even possible?
EDIT: Solved the problem. Files were transpiled and concatenated in one single js file. Now I removed the concat-process and it works.
Solved the problem. Files were transpiled and concatenated in one single js file. Now I removed the concat-process and it works.

Resources