A hobby project. A naive and simplified React implementation
npm install @faiwer/reactA 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
- preact is about ~10 KiB
- JSX
- Functional components
- Class components (limited)
- Hooks:
- useState
- useRef
- useMemo, useCallback
- useStableCallback (a better version of useEffectEvent)
- useLayoutEffect, useEffect (improved versions)
- useId
- useContext
- Refs
- Context
- Portals
- Fragments
- Hot Module Replacement
- Preact Dev Tools (partially)
- npm uninstall react react-dom @types/react @types/react-dom
- npm i --save react@npm:@faiwer/react
- npm i --save --force react-dom@npm:@faiwer/react-dom
- You might need to update your tsconfig.json (no necessarily):
``json`
"compilerOptions": {
"jsx": "react-jsx",
}
"jsx": "preserve"
Or use eslint-plugin-react
- If you're using than configure this in your .eslintrc:`
json`
"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 :-)
To mount an app:
`tsx`
const container = document.getElementById('root');
createRoot(container).render(
To show your app in the Preact DevTools:
`tsx`
import { preactDevTools } from 'react/debug';
// …
createRoot(container).render(
import.meta.env.DEV ? { preactDevTools } : undefined
The 2nd argument also supports:
`tsxtrue
{
// If 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'
),
}),
- JSX: Math namespace
- isStaticChildren
- Make all hooks pure
- Resolve "TODO: add a test" comments
- leverage in jsx()
… and probably never will:
- Class Components: getSnapshotBeforeUpdatememo
- Synthetic events
- Portals:
- Event bubbling from portals
- Rendering multiple portals in the same DOM node
- (because components are memoized by default)useInsertionEffect
- Some less popular tools
- useOptimistic
- (could be polyfilled)useDeferredValue
- (could be polyfilled)useDebugValue
- (dev tools are not supported)
- .
- .preconnect
- , prefetchDNS, preinit, preinitModule, preload, preloadModuleuseTransition
- Modern stuff:
- , startTransition
- , lazy.useActionState
- Form-based hooks (like , useFormStatus)__REACT_DEVTOOLS_GLOBAL_HOOK__
- React Dev Tools. Just take a look at , it's huge. E.g., it has reactDevtoolsAgent, a class with 20-30 methods…flushSync
- (not supported by the engine)
- SSR
- 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 , cacheSignal, or use`. Too much work. It took React many years to cook it well :)