Playground for experimenting with Solid.js <> React interoperability
This sample code should serve as early proof-of-concept interoperability between Solid.js and React components within a single application. The goal is to promote Solid.js adoption while allowing the development community to take advantage of the large number of existing React component libraries. If successful, the longer term vision would be a custom compiler (e.g. a Babal loader) that can efficiently merge hybrid React and Solid.js projects.
This project uses NPM and Webpack for both Solid and React compilation.
To run the project, install dependencies, and run the development server:
npm install
npm run dev
The basic premise behind the current functionalty is to wrap React-in-Solid or Solid-in-React by using helper higher-order component functions.
Consuming a single React component from a Solid is as follows:
import { Button as ReactButton } from 'some-react-library/components';
import { reactToSolid } from '../compat';
const Button = reactToSolid(ReactButton);
export function SolidComponent() {
return (
<div>Click Me: <Button text="OK" /></div>
);
}
To avoid complicated aliasing and renaming of several components in a given file, the wrapper function also accepts an object containing properties which are components:
import { Label, Button } from 'some-react-library/components';
import { reactToSolid } from '../compat';
const SC = reactToSolid({
Label,
Button
});
export function SolidComponent() {
return (
<div>
<SC.Label text="Click Me" />
<SC.Button text="OK" />
</div>
);
}
- Host standalone React Component (no props)
- Pass props into React Component
- Trigger React re-render on prop update
- Receive callback events from React Component
- Unmount React Component on cleanup
- Demo fully-controlled components
- Component / Element props
- Child elements
... TBD
- Host standalone Solid Component (no props)
- Pass props into Solid Component
- Trigger reactive updates on prop update
- Receive callback events from Solid Component
- cleanup Solid Component on unmount (untested)
- Demo fully-controlled components
- Component / Element props
- Child elements
- Context API (React -> Solid -> React)
... TBD
JSX compilation is by far the most interesting facet of a hybrid Solid.js - React codebase. There are a number of possible approaches that could be attempted:
- File path disambiguation
- In-file designation
- Dual-mode compilation
Using webpack, this is the simplest approach and one used by the demo application, currently. The current implementation has multiple module loaders declared for JSX files. If a .jsx
file is found within the src/react
directory it is processed with the babel loader @babel/preset-react
; all other JSX files are processed with babel-preset-solid
. A similar approach would be to use, distinct file extensions such as .jsx
, .s.jsx
, or .r.jsx
could to designate which babel presets to use for a given JSX file.
import { ReactComponent } from '../react/ReactComponents';
import { reactToSolid } from '../compat';
const SC = reactToSolid({
ReactComponent
});
export function SolidComponent() {
return (
<div>
<SC.ReactComponent
numberProp={1} // OK
callbackProp={() => { }} // OK
componentProp={<b>Text</b>} // Does not work!
/>
</div>
);
}
This approach may or may not be possible; but will likely require writing a custom loader which can determine whether the JSX contained within it is Solid or React, similar to Webpack "magic comments" or ESLint directives (likely in the forms of special comments.
This would require a custom loader:
/* jsx-preset react */
export function ReactComponent() {
// ...
}
Alternatively, it could be possible to declare the JSX mode at the import level by using built-in webpack syntax and loader aliases:
// in this case, the Solid is the default JSX, and we opt-in to React compilation at import time:
import { ReactComponent } from 'babel-loader-react!../components';
export function SolidComponent() {
// ...
}
This approach would be the pièce de résistance of hybrid Solid / React applications. Requiring further research and writing of a custom Babel loader, a hypothetical future-implementation might look something like:
import { SolidComponent } from '../components';
import { ReactComponent } from '../components/legacy';
import { JSX } from '../compat';
// Assume this is Solid JSX by default:
export function HybridComponent() {
return (
<>
<SolidComponent />
<JSX react>
<ReactComponent />
</JSX>
</>
);
}