@faiwer/react
v19.0.4
Published
A hobby project. A naive and simplified React implementation
Maintainers
Readme
What is it?
A naive React implementation. Why? What's wrong with the existing one? Nothing. I just wanted to implement it from scratch by myself. It can be used as a drop-in replacement for some simple React apps. May require some trivial changes, though.
A few stats:
- ~5.5k LoC in TypeScript
- ~122 KiB: transpiled JS code
- ~44 KiB: minified by
terser - ~16 KiB: minified gzipped
preactis about ~10 KiB
It supports
- JSX
- Functional components
- Class components (limited)
- Hooks:
useStateuseRefuseMemo,useCallbackuseStableCallback(a better version ofuseEffectEvent)useLayoutEffect,useEffect(improved versions)useIduseContext
- Refs
- Context
- Portals
- Fragments
- Hot Module Replacement
- Preact Dev Tools (partially)
Installation
npm uninstall react react-dom @types/react @types/react-domnpm i --save react@npm:@faiwer/reactnpm i --save --force react-dom@npm:@faiwer/react-dom- You might need to update your
tsconfig.json(no necessarily):
Or use"compilerOptions": { "jsx": "react-jsx", }"jsx": "preserve" - If you're using
eslint-plugin-reactthan configure this in your .eslintrc:"settings": { "react": { "version": "19" } // not 'detect' }, - Good luck. If your project is big enough, I'm pretty sure you got a ton of type errors. Sorry :-)
Usage
To mount an app:
const container = document.getElementById('root');
createRoot(container).render(<App />);To show your app in the Preact DevTools:
import { preactDevTools } from 'react/debug';
// …
createRoot(container).render(
<App />,
import.meta.env.DEV ? { preactDevTools } : undefinedThe 2nd argument also supports:
{
// If `true` the lib will make more checks and add the `__fiber` field for each
// generated DOM Node to simplify debugging
testMode: boolean,
// A hook to improve local error stack traces. Provide a method that converts
// __filename into an internal URL that your DevTools can handle.
// E.g., this worked out for Vite:
transformSource: source => ({
...source,
fileName: source.fileName.replace(/^.+\/src/, location.origin),
}),
// And this worked out for Webpack:
transformSource: source => ({
...source,
fileName: source.fileName.replace(
/^.+\/src/,
'webpack://your-project-package-name/src'
),
}),TODO
- JSX: Math namespace
<Lazy/>- Make all hooks pure
- Resolve "TODO: add a test" comments
- leverage
isStaticChildreninjsx()
It does NOT support
… and probably never will:
- Class Components:
getSnapshotBeforeUpdate - Synthetic events
- Portals:
- Event bubbling from portals
- Rendering multiple portals in the same DOM node
memo(because components are memoized by default)- Some less popular tools
useInsertionEffectuseOptimistic(could be polyfilled)useDeferredValue(could be polyfilled)useDebugValue(dev tools are not supported)<StrictMode/>.<Profiler/>.preconnect,prefetchDNS,preinit,preinitModule,preload,preloadModule
- Modern stuff:
useTransition,startTransition<Suspense/>,lazy.
- Form-based hooks (like
useActionState,useFormStatus) - React Dev Tools. Just take a look at
__REACT_DEVTOOLS_GLOBAL_HOOK__, it's huge. E.g., it hasreactDevtoolsAgent, a class with 20-30 methods… flushSync(not supported by the engine)- SSR
Major differences
- It renders HTML-comment for nullable nodes and some fragments. Why? It helps a lot to keep the reconciliation algorithm simple. Took this idea from Angular.
- No synthetic events. I don't see any reason to implement them.
- All components are memoized by default. Why not?
- Not too much custom DOM-related code. This library is supposed to be simple and silly. Whereas React-DOM lib is huge.
- No modern fiber-driven stuff like
<Suspense>,cacheSignal, oruse. Too much work. It took React many years to cook it well :)
