11 ms·
Show HN: Forgo.js – a 4kb React alternative based on plain JS and DOM APIs
- jeswin 6y agoHi HN. I built Forgo because React is no longer "it's just JS" (actually it never was). The patterns, practices and vocabulary used in the React ecosystem present considerable hurdles to a newbie - hooks, reducers, actions, flux etc. All of them are React specific, and locks the developer into framework related knowledge. But there is one idea from React which made a ton of sense; from the time I started using it nearly seven years back. That's JSX - instead of shoehorning expressions into markup, move markup into the programming language. Forgo embraces JSX and skips most of the other ideas in React in favor of a more plain vanilla approach.
- chrisabrams 6y agoI don't consider reducers, actions, redux/flux to be React specific, even if they are popular in the React world. I know just as many people who don't use those in the React world. IMO what React did was push forward the single data flow paradigm. What should a developer do that needs to share state across a Forgo app? It would be awesome if browsers just had a real template language like JSX, we'd see more "web components" which are just a little bit of JSX/or whatever templates and some DOM APIs. Developers are tired of managing binding and unbinding events. React does that well. I assume Fargo does too. So putting aside the redux/flux paradigm, what else is the real hurdle being solved?
- jeswin 6y ago> What should a developer do that needs to share state across a Forgo app? We already have many facilities in JS to handle state. Singleton patterns, closures etc. The example on the website uses closures to handle component state - which is simpler than React's hooks based approach. function SimpleTimer() { let seconds = 0; // Just a plain variable return { render(props, args) { setTimeout(() => { seconds++; rerender(args.element); // rerender! }, 1000); return <div>{seconds} secs have elapsed...</div>; }, }; } > I don't consider reducers, actions, redux/flux to be React specific They didn't emerge from React - but it isn't possible to do React without them. When I conduct trainings, I often see some of my students go - wow, why would you do that? So Forgo basically is an exploration of how to simplify things - and to let users pick their desired level of complexity.
- dceddia 6y agoI understand where the Redux terminology gets mixed up with React and people believe they're one and the same, but it's totally possible to do React without touching reducers, actions, or Redux. Maybe you mean it's not realistic to write React "beyond toy examples" or "in the real world" without Redux, but still, I'd beg to differ there. You can get very far with plain React and the context API and other libraries. Redux definitely isn't required. Forgo does look cool though! I like the idea of using plain variables for state, and returning an object allows you to add other things to that object (didMount, didUpdate, unmount etc).
- michaelpb 6y agoYeah, even with large-ish projects, just by refactoring some of your state manipulation into separate libraries that you manually hook into your top-level components, you can get by quite fine without a whiff of Redux. That said, this is a cool framework, congrats on completing it! I'm also working on a minimalist React-ish framework though in a very different approach.
- chrisabrams 6y ago> They didn't emerge from React - but it isn't possible to do React without them. It is 100% possible to build large React apps without redux/flux. There are entire ecosystems built around, say Mobx, among many other libraries and tools which don't even follow the flux style patterns. > The example on the website uses closures to handle component state I'm not asking about component state; I'm asking how do multiple components access app-wide state. I can't use a singleton for that; how do I know when that singleton is updated? I guess I could use a proxy but I don't want to do that. > and to let users pick their desired level of complexity. React is the view layer. It doesn't enforce any type of data layer or state management layer.
- jeswin 6y ago> I can't use a singleton for that; how do I know when that singleton is updated? It's up to the developer to make that decision. An AccountInfo control could be listening to "ACCOUNT_INFO_CHANGE" events (emitted when account info changes), and can subsequently rerender and read off some singleton state. Rerenders are explicit. And like you said, proxies could be another option.
- spankalee 6y agoI work on a templating library called lit-html that aims to just solve the declarative DOM w/ property & events bindings problem: https://lit-html.polymer-project.org/ https://lit-html.polymer-project.org/ It's plain JS with a very similar DX to JSX, and I think addresses that really well. My team also works closely with Chrome, Safari, and Firefox on proposals like Template Instantiation that aim to build up more APIs that template libraries can use to be smaller, faster, and more interoperable. I really think we will eventually get good templating in the DOM so at least the most common use-cases are taken care of.
- no_wizard 6y agoWhere is the working group at with that proposal? I've been eyeing it closely, but it hasn't seen any significant movement in over two years, last I checked. I'm excited about template instantiate, though I worry the API is going to be tied to having template element instances, instead of being part of the HTMLTemplateElement and its own thing I could take advantage of with string templates. I also haven't seen any place where I can give feedback formally on the proposal to the working group :( HTML imports were supposed to be the holy grail to solve this, but nobody but Chrome supported it :(
- spankalee 6y agoIt's being discussed mainly in the WICG web components GitHub repo. There are recent issues in here: https://github.com/WICG/webcomponents/issues https://github.com/WICG/webcomponents/issues
- eyelidlessness 6y agoEvery time I look at lit I get this unsettling feeling that it’s almost what I want but can’t be, because it loses the only thing I want in JSX. Lit fundamentally targets templates with HTML output. Which is great for what it is. But the thing JSX offers that nothing else does is: - return values are a data structure, so they’re render-target/render-implementation agnostic - it’s compile time only/effectively a macro, so the compiler can choose how to render subtrees I love the “it’s actually part of the language not a build tool” part of lit, but it can’t satisfy those other goals without giving up its core selling points.
- varispeed 6y agoReact is nothing new - these patterns that are being used have been with us for decades. Sure you can go about programming without knowing these patterns, but looking for ways around in order to know and understand less is a dead end. If you can't understand what hooks, reducers or actions do, why not spending more time on it? Sooner or later a newbie will encounter more complex project and having themselves shielded from knowledge will likely cause them to give up.
- gfxgirl 6y agoHooks IMO are a poor design. Calling a function that does something different with the same parameters is an anti-pattern. const [v, setV] = useState(123); First call v gets set to 123, second call it doesn't. WAT!
- nwienert 6y agoWhat? Functions can do anything, even outside of hooks you can have memoized functions. Algebraic Effects are the thing I'm most glad React introduced, hooks are nothing short of a revolution for UI.
- eyelidlessness 6y agoFunctions which return different data for the same input aren’t functions. It’s a bad interface.
- Chernobog 6y agoThat's a very general statement. I wouldn't accuse you of implying that it would also apply to e.g. Date.now(), but I firmly disagree that it applies to hooks. I get that hooks are declared in the form of functions, and that pure functions are the desired pattern in many cases. But I like to think of hooks as hooks, not functions (emphasized by the naming convention of prefixing with "use"). We are in the context of React, after all, and I think it is comparable to critique of JSX because of "separation of concerns". Yes, valid critique in many cases, but not necessarily applicable to React. Having been a React developer for over 5 years now, I think hooks solve a lot of problems that before would typically have been solved with Redux, Redux Saga and other libraries. Or with home made solutions, leading to various degrees of mess. From what I've seen, hooks have enabled React devs to write code that is even easier to understand, while also keeping things simpler and external libraries fewer.
- spankalee 6y agoJSX isn't "just JS" either. Did you consider using plain JS? You can still move markup into script.
- k__ 6y agoYes, the arguments here are ridiculous. Everything that IS actually just JS is listed (hooks, actions, reducers), but the only thing that isnt (JSX) is used without a question. Sometimes React critique just reads like an elaborate trolling attempt.
- eyelidlessness 6y agoJSX being a macro-like compile time primitive is actually its biggest strength. It’s a data structure but its implementation is undefined, so JSX implementations are free to compile it to whatever is most suitable for its runtime. Including compiling it away to nothing. “It’s just JS” can certainly do that but you need additional compile time signals.
- keyle 6y agoI'm on the opposite side. I think JSX is a horror show. We've been trying to remove view from logic since the 90s, and move towards declarative views with data binding. Keep your nook and crannies away from the presentation layer. But all the industry wants is shoving PHP in the browser. I think it's sad. We're making the UI layer always more complicated. Let's say your UI changes, like a redesign... you can rewrite your component and probably need to change most of them. If your UI was just dumb and declarative and the data was salt on top, changing the UI is trivial. The UI should declare what the UI should look like, not describe how it should do it. This is why, as an old timer, I much prefer the direction VueJS took. Edit: great lib though, anything small is a win.
- postalrat 6y agoTill you want to render a list or something. Then your vue template starts looking a lot more like javascript.
- keyle 6y agoThat's plain wrong, it's still all inline with the markup, and more importantly, it doesn't reveal any of the inner working of the list.
- daveidol 6y agoPeople always talk about separation of HTML and JS is a separation of concerns, but it's really not in my experience. It's separation of technologies. The JS has to grab DOM nodes by class or ID and is highly coupled to its structure anyway, so putting them together (as React does) seems far more logical in my opinion. Separating them into files and using string classes to look them up is far less easier to read and leads to runtime issues (since a typo isn't actually a code error but just silently doesn't work). JSX is really not like PHP at all. PHP just writes out strings - JSX is all JavaScript objects. What you are describing is more akin to something like Handlebars or Angular templates. Plus, JSX is just sugar and is not required to use React if you prefer something like hyperscript (no compile step needed in that case).
- catacombs 6y agoThanks for the implementation. How does Forgo handle state?
- arendtio 6y agoActually, JSX is the thing that keeps me from using React ;-) I prefer the template syntax of .vue files or similar to the one riot.js uses. They use basically the same syntax html uses, just on a smaller scale and limited to a single component. That way you don't have to invent new was of embedding markup in code.
- eyelidlessness 6y agoI’m curious if you’ve looked at SolidJS, which has similar goals.
- cccc4all 6y agoMost developers don't understand Javascript, let alone React. I recommend developers to look into history of React, why they chose to build it in first place and search for youtube videos explaining React concepts. https://reactjs.org/blog/2013/06/05/why-react.html https://reactjs.org/blog/2013/06/05/why-react.html React is about data flows and managing state transitions with user experience. Lot of these React replacement projects simply try to reinvent the wheel. Reminds me of all the jQuery replacements that were attempted, but quickly realized that in order to support basic features, they had to recreate jQuery.
- RobertKerans 6y agoYes, this is all spot on, though I'd say the majority of developers actually building real things do understand JS, and would have a look at this, and dismiss it for the reasons you stated.
- dsego 6y agoSince there is no setState/useState, how does it know when to render? Does it render after every client event, like mithril?
- jeswin 6y agoIt's manually triggered - you've to call rerender() which re-renders the specific component.
- have_faith 6y agoIf preserving the purity of JS is a goal it probably makes sense to not present examples in TypeScript. TS is awesome but if I know TS I know how to add types and if I don't know TS the examples become hard to follow (remembering from when I didn't know TS). I guess it comes down to your target audience but my assumption is Forgo is targeted more towards people who prefer "traditional" for lack of a better word JS concepts. The biggest red flag I noticed was the use of the "rerender" function. It reminds me a bit of writing code in jQuery and manually having to keep track of when the DOM needs updating. Obviously not a huge burden for something small but would be very interested in how it effected larger apps. Seems like a big tradeoff to have to take on that responsibility again. Saying that, the general API of the library is easy to understand and I could imagine being productive with it very quickly.
- jeswin 6y agoPoint taken on TypeScript, I'll clean up that example later today. Perhaps I should move TS to a separate section. I totally expected rerender to be a bit controversial. But if you look at React, the user does provide (strong) hints in the form of the setState() invocation - which could be done here as well. But then again, it's a tradeoff. Simplicity, and Python's Explicit is better than implicit.
- dfee 6y agoI disagree. At worst you can let a user switch between a TS / JS code block, but really we don’t want to hunt around your .d.ts files to discover what the docs should tell us.
- lux 6y agoOr make it a toggle instead of a separate section. I really appreciate being able to toggle between languages in API docs.
- eyelidlessness 6y agoI’d highly recommend keeping TS first class. There are components/tools available to group TS/JS examples in tabs and let the user consume whichever they prefer. (Forgive me for not linking I’m on phone and gotta run.)
- aphextron 6y agoOk, I'll say it. A 4k hobby project is not a React alternative. It's a view rendering library.
- jeswin 6y agoYou make a valid point, I am upvoting you. It's a view rendering library, but it also does state management while not actually doing it. Forgo just makes some design decisions that makes state management not require any code in the library. So, it does what the react and react-dom packages do - it's not just the view.
- pier25 6y ago> but it also does state management while not actually doing it Very similar to how Mithril and Imba solve state management: just use vanilla objects/primitives.
- deedubaya 6y agoIt’s backbone but with jsx
- varispeed 6y agoWhat is the purpose for such libraries other than author(s) having cool entry in their CV? These things don't create anything original and are only polluting already heavily polluted JS ecosystem. When are we going to see actually something interesting?
- turbinerneiter 6y agoUnwrapping and unearthing core principles. Often a new thing, like react, has a very epic core idea, but the implementation leaks through, and/or it is interlaced with other ideas and concepts. Projects like this are about replicating a core concept or an idea without the other stuff around it. I don't have a good description for it yet, but I feel like I have seen this a couple of times now: a new i.e. product/tech/library comes out with one ore more interesting new concepts, and afterwards, over time, these core concepts get unbundled and isolated. From there, you can remix it with other ideas. It's like the first wheel came on a car, then someone took the wheels off and played with them to invent the motorcycle. Also that maybe was the shittiest made up example in history, but I hope you get the idea.
- ZephyrBlu 6y agoPeople re-invent the wheel because they think they can do it better.
- chrisabrams 6y agoI've taken the example from your homepage and made it in React without using redux or hooks. import React, { Component } from 'react' class ManualStateComponent extends Component { state = { now: null } constructor(props) { super(props) } // Since you don't want to call `setState`, here is a method with the name you prefer rerender() { this.setState({ now: Date.now() }) } } // Example Component class SimpleTimer extends ManualStateComponent { constructor(props) { super(props) this.seconds = 0 // Just a plain variable } componentDidMount() { setTimeout(() => { this.seconds++; this.rerender() // rerender! }, 1000) } render() { return <div>{this.seconds} secs have elapsed...</div>; } } gist: https://gist.github.com/chrisabrams/9e140d212098033365a76666a4926af1 https://gist.github.com/chrisabrams/9e140d212098033365a76666...
- chrismorgan 6y agoAnd another comparison, SimpleTimer.svelte: <script> let seconds = 0; setInterval(() => { seconds++; }, 1000); </script> <div>{seconds} secs have elapsed...</div> Note that this deliberately functions slightly differently: it uses setInterval rather than repeated setTimeouts. This makes a much more accurate seconds counter, as setTimeout will wait for at least 1000 milliseconds before incrementing seconds and setting another timeout, meaning that you will always count time too slowly, commonly around 1% too slow, but more at times like if JavaScript execution is being throttled because the page is in the background. If you really wanted to do it the other way, it’d be something like this: <script> let seconds = 0; function increment() { seconds++; setTimeout(increment, 1000); } setTimeout(increment, 1000); </script> <div>{seconds} secs have elapsed...</div>
- hombre_fatal 6y agoThe improvement to this sort of time-tracking is not setInterval vs setTimeout but rather "<div>{(Date.now()-startedAt)/1000} secs have elapsed</div>". That way your clock loses its dep on interval frequency.
- ibraheemdev 6y agoThis looks really cool. Side note: Is it just me, or is there a new "NKB alternative to X.js" everyday?
- cuddlecake 6y agoExcept for Angular2
- daveidol 6y agoLike others have said - I think the documentation glosses over how the "rerender" function totally and drastically changes this from a "React alternative" to "backbone with JSX". The point of React is not the JSX, but rather the unidirectional data flow and automatic re-renders due to prop changes -- essentially modelling your application as a "state machine" and eschewing the need for a "view model" or manual synchronization of DOM state to application state. With the "rerender" function I fear any nontrivial application will devolve back into the tangled web of event listeners we left behind when moving to React. Personally - I think a library like this is only useful for a very narrow audience: people with an affinity for JSX that are only building webpages (not web "apps") with some basic progressive enhancement features that can be kept track of pretty easily. I don't mean to be overly negative here - I just suggest that you alter the documentation to be more clear about the differences/limitations when compared to react. The existing docs/example is almost (unintentionally, I'm sure) misleading in my opinion.
- spiffytech 6y agoI've been building some projects with Crank.js and Mithril, both of which require an explicit function call to rerender the application after changing state. In theory, that sounds terrible; I don't want to keep track of when my app needs to rerender, or worry about a stale DOM because I forgot to rerender after some event. In practice, it's a breath of fresh air compared to my work with React/Vue/Svelte. Everything is just so simple; I can use vanilla JS data structures and code organization patterns. I can architect my frontend state and logic in the way that makes the most sense for my domain, instead of ceding control flow to a heavy, footgun-y, limited-expressivity framework that demands to know everything my app does so it can decide on its own when to rerender. I'm tired of debugging confusing bugs because React hooks mix asynchronous control flow inside the render loop and my app has to have correct rendering and logic for every intermediate state of a chained sequence of useCallback/setState hooks. Tired of telling my team we have to put off a feature because it'll take a week to do something that feels like a 3-hour task because refactoring around a framework's control structures is harder than refactoring ordinary JS features. Tired of magic reactive compilers that create buggy renders and template languages that are only nice to work with if you use the One True Blessed VS Code Plugin, and tired of frameworks that need CLI scaffolding tools because they're more complicated than anyone wants to initialize with a blank editor. Crank.js, Mithril, and (at a preliminary glance) Forgo are very compelling to me. They offer the declarative, self-contained nature of components that I love from React/Vue/Svelte and the type safety of TypeScript for view code instead of template strings. My whole project scaffolding is just installing esbuild and generating a tsconfig.json. Maybe toss in browser-sync and watchexec if I'm feeling fancy. I don't need to make major architectural decisions based on the preferred state management tool, because these frameworks couldn't care less about how state is managed. And if I really want automatic rerenders, it's 10 LOC to write a reusable ES6 Proxy that returns an object I can dump state values into that will auto refresh if I reassign a property. And these frameworks still feel like a state machine in the same way React does - view code is declaratively rendered from application state. There is synchronization of state to DOM, but only in the sense that you have to explicitly invoke the declarative engine, and not in the sense that you have to manually synchronize like you would using jQuery to find and change DOM nodes. Keeping up with manual refreshes feels so simple and easy compared to all the hoops I've jumped though with the household name frameworks.
- dsego 6y agoReminds me of mithril+jsx, only that mithril returns an object with a view() method instead of render() and has an auto-redraw system. https://mithril.js.org/jsx.html https://mithril.js.org/jsx.html
- azangru 6y agoI can't believe nobody has mentioned Preact yet...
- GroSacASacs 6y agoalmost every view framework is based on plain JS
- GroSacASacs 6y agoPlain JS ? Not even that is true, it uses JSX ...
- c-smile 6y agoLet me try too :) Reactor - exactly 0kb (zero) ReactJS alternative built-in into Sciter.JS :) This https://github.com/c-smile/sciter-js-sdk/blob/main/samples.sciter/reactor/component-clock.htm https://github.com/c-smile/sciter-js-sdk/blob/main/samples.s... works in Sciter.JS as it is - without any external frameworks. `JSX` and `element.patch(jsx)` are built-in into Sciter.JS. And it uses ES2020 so no Babel step is required.
- no_wizard 6y agoI like sciter, but this is targeted at compile time desktop environments (and mobile, one day!) right? Which actually makes it even more amazing, but that explains the 0KB for those who don't recongize the framework by its name :D Everyone should check out sciter for their next project if you're considering something like Electron, though! https://sciter.com/ https://sciter.com/
- simplify 6y agoMithril.js another "plain JS and DOM" React-like library. It's has been around for a long time now with a healthy community, so it's pretty robust.
- narrator 6y agoSorry to be a cynic here, but here's another 4k better than React library from last week: https://news.ycombinator.com/item?id=25677272 https://news.ycombinator.com/item?id=25677272 It seems like lightweight web frameworks are the "Hello World!" of Show HN.
- timdaub 6y agoHere I am again. Stop using JSX and start embracing Javascript's template strings. They're awesome! https://github.com/developit/htm https://github.com/developit/htm
- jeswin 6y agoForgo has gotten better in the last couple of days, thanks to contributions. - Forgo Router https://github.com/forgojs/forgo-router https://github.com/forgojs/forgo-router - Error boundaries for sub-component error handling - CodeSandbox Links https://forgojs.org/#try-it-out-on-codesandbox https://forgojs.org/#try-it-out-on-codesandbox