{"id":18032339,"url":"https://github.com/dmitriz/un","last_synced_at":"2025-03-27T05:31:11.708Z","repository":{"id":20674592,"uuid":"90528454","full_name":"dmitriz/un","owner":"dmitriz","description":"unframework for universal uncomponents - use your uncomponents with no boundaries","archived":false,"fork":false,"pushed_at":"2024-03-27T02:04:23.000Z","size":2106,"stargazers_count":30,"open_issues_count":21,"forks_count":3,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-04-27T09:43:10.752Z","etag":null,"topics":["uncomponents","unframework"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dmitriz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-05-07T11:38:42.000Z","updated_at":"2024-07-20T18:03:13.117Z","dependencies_parsed_at":"2023-12-26T23:22:52.487Z","dependency_job_id":"c2b16552-e642-434a-95c3-1c8cb9be37dc","html_url":"https://github.com/dmitriz/un","commit_stats":{"total_commits":363,"total_committers":12,"mean_commits":30.25,"dds":"0.46005509641873277","last_synced_commit":"6527b385bc0de9e84fc65cde66c746191ecf1d4d"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmitriz%2Fun","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmitriz%2Fun/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmitriz%2Fun/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dmitriz%2Fun/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dmitriz","download_url":"https://codeload.github.com/dmitriz/un/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245791393,"owners_count":20672665,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["uncomponents","unframework"],"created_at":"2024-10-30T10:13:03.639Z","updated_at":"2025-03-27T05:31:10.795Z","avatar_url":"https://github.com/dmitriz.png","language":"JavaScript","readme":"```\n    __   __  _____   \n   / /  / / / ___ \\   ( Logo inspiration from MostJS\n  / /  / / / /  / /     https://github.com/cujojs/most )\n / /__/ / / /  / /      \n \\_____/ /_/  /_/\n```\n\n# un.js\n\n\n\nUnframework for Universal Uncomponents\n\n\u003e **We do not think in terms of reusable components.** \n\u003eInstead, we focus on reusable *functions*.\n\u003eIt is a functional language after all!\n\u003e-- [Scaling The Elm Architecture](https://guide.elm-lang.org/reuse/)\n\n### Contributions and Feedback are highly welcome!\n\n## Quick start\n\nInstall [the `un.js` package](https://www.npmjs.com/package/un.js) with\n\n```sh\n$ yarn add un.js\n```\nor with\n```sh\nnpm install un.js -S\n```\nor with [`pnpm`](https://github.com/pnpm/pnpm)\n```sh\npnpm install un.js -S\n```\nand try the [active counter example](https://github.com/dmitriz/un/tree/master/examples/active-counter) or read the introduction below.\n\n## Philosophy and Universality\n\n- Write your business logic as pure functions with no external dependencies\n- No side-effects, testable by your favorite test runners\n- No external imports, no packages, no libraries\n- No extension of proprietary component classes (Backbone, React, ...)\n- No lock-ins, your uncomponents should be usable as plugins with or without any framework\n- Reuse your code and share with others accross frameworks with no boundaries\n\n\n## Why unframework?\n\n- Frameworks try to provide and cater for everything, `un` tries the opposite - give you the maximal possible freedom.\n\n- Frameworks try to tell you exactly what to do, `un` tries the opposite - staying out of your way.\n\n- Frameworks make your code coupled with proprietary syntax, `un` lets you write your code with plain JavaScript functions, undistinguishable from any other functions. There is not a single trace of `un` in any of your functions.\n\n- Frameworks often like you to inherit from their proprietary classes, `un` tries to help you embrace the pure functional style and minimise use of classes and `this`. However, this is merely suggestive. Giving you maximum freedom and staying out of your way is a higher priority for `un`.\n\n\n## What is provided?\n\nCurrently a single tiny factory function called `createMount`. [See here the complete code.](https://github.com/dmitriz/un/blob/master/index.js) Its role is similar to `React.render`, in which you would typically see it in only few places in your app. \n\nHere is a usage example. Instead of learning new API, new framework or long set of new methods, your simply import your favorite familiar libraries that you are already using anyway:\n\n\n```js\nconst mount = createMount({ \n\n  // your favorite stream factory\n  // mithril/stream, TODO: flyd, most, xstream\n  createStream: require(\"mithril/stream\"),\n\n  // your favorite element creator\n  // mitrhil, TODO: (React|Preact|Inferno).createElement, snabbdom/h, hyperscript\n  createElement: require('mithril'),\n\n  // your favorite create tags helpers (optional)\n  createTags: require('hyperscript-helpers'),\n\n  // mithril.render, TODO: (React|Preact|Inferno).render, snabbdom-patch, replaceWith\n  createRender: element =\u003e vnode =\u003e require('mithril').render(element, vnode)\n})\n```\n\nSo instead of having external dependencies in *every file*, \n`un` simply lets you provide those libraries **once** and return the `mount` function, the only function from `un` that you need. The role of the `mount` is similar (and inspired by) [`Mithril` `m.mount`](https://mithril.js.org/mount.html) or `React.render` with auto-redrawing facility. Our key vision is, attaching a live component to an element should be as simple as calling a function and `mount` does exactly that:\n\n\n```js\n// mount our live uncomponent and get back its writeable stream of actions\nconst actions = mount({ element, reducer, view, initState})\n```\n\nSo we call `mount` with 4 basic properties:\n\n- `element`: HTML element, where we attaching our uncomponent, similar to `React.render` the element's content will be overwritten\n\n- `reducer`: Redux style reducer from our model logic\n\n- `view`: Plain pure function taking `dispatcher` and `state` and returning new `state`, the state can be global, narrowed down, or completely local to the uncomponent, to cater for the [fractal architecture](https://staltz.com/unidirectional-user-interface-architectures.html). The view function dispatches actions just like in Redux and returns a virtual or real DOM element, depending on the library used in configuring the `mount`. But to be completely pure with no external dependency, the `view` must include the element creator factory as one of its parameters:\n\n```js\n// all parameters are explicit, no dependencies, no magic\n// can be tested as pure dumb function in any environment\nconst view = h =\u003e (state, dispatch) =\u003e \n  h('div', `Hello World, your ${state} is wonderful!`)\n```\n\nwhere `h` stands for our favorite element creator passed to `createMount`. We find the [`hyperscript`](https://github.com/hyperhype/hyperscript) API supported by many libraries (e.g. Mithril, Snabbdom, or [`react-hyperscript`](https://github.com/mlmorg/react-hyperscript)) most convenient, but using JSX should also be possible as it is equivalent to the `React.createElement` calls.\n\nOr use the `createTags` helpers (like [`hyperscript-helpers`](https://github.com/ohanhi/hyperscript-helpers)) that you can conveniently destructure inside the view:\n\n```js\n// again, no dependencies, only function parameters, \n// all inputs are instantly visible, no need to jump elsewhere\nconst view = ({ div }) =\u003e (state, dispatch) =\u003e \n  div(`Hello World, your ${state} is wonderful!`)\n```\n\nThe other two parameters of the `view` are `dispatch` and `state` that match the types (or more precisely, interfaces) of the `action` and the `state` parameters of the `reducer`. (Note how the `view` signature matches the one of the `reducer`. Further, it also matches the [native JS `Array.prototype.reduce`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce) as well as [the general `reduce` method](http://ramdajs.com/0.18.0/docs/#reduce) signatures, the latter provided by the [Foldable Typeclass](https://github.com/fantasyland/fantasy-land#foldable).)\nThe state, updated by the reducer, will be passed directly to the view (from the same `mount` call). And every action value used to call the `dispatch` function, will be passed directly as action to the `reducer`. For example, calling `dispatch('foo')` in the event handler inside the `view` will result in `foo` being passed as `action` to the `reducer`.\n\nThis style of writing was inspired by https://github.com/ericelliott/react-pure-component-starter and https://medium.com/javascript-scene/baby-s-first-reaction-2103348eccdd\n\nIn `React` the role of the `view` would be played by the component `render` method, but we already have another static method `React.render`, so we prefer to call it the `view` as in Mithril. \n\n\n- `initState`: The state to initialise our uncomponent.\n\n\n## Uncomponents\n\nWhy \"uncomponent\"? Because there isn't really much of a \"component\", the `reducer` and the `view` are just two plain functions and the initial state is a plain value. \n\n### So what is called \"uncomponent\"?\n\n- Native JavaScript functions. [Or native generators](https://github.com/funkia/turbine/#understanding-generator-functions). Or native object holding a few functions.\n- No proprietary syntax. Every part of the framework, library, package is hidden away from the user. In the configuration. \n- Pluggable into any framework. Or into no framework. This is what configuration is for.\n\n\n\n## Streams\n\nStreams are in the core of `un`. [The introduction to Reactive Programming you've been missing](https://gist.github.com/staltz/868e7e9bc2a7b8c1f754) by Andre Staltz is a great introduction to streams. [`flyd`](https://github.com/paldepind/flyd) is a great minimal but powerful stream library to use, including great examples to see the streams in action. The [Mithril stream library](https://mithril.js.org/stream.html) is even smaller but suffices to let `un` do its job. Note that some libraries, such as [`most`](https://github.com/cujojs/most) distinguish between \"pending\" and \"active\" streams, but to make things as simple as possible, all streams in `un` are always active, readable and writeable. \n\nDespite of their initial complexity, streams model very well the asynchronous processes such as user acton flow, and consequently help to greatly simplify the architecture. The state values are stored directly inside the stream, so no stores such as in Redux are needed.\n\nInstead of letting the framework do some \"magic\" behind the scene, when updating the DOM, with `un`, your view listens to its state stream. Whenever the state changes, its new value is passed to any subscriber, or which the view is one. The view function is pure with no side-effects, so all it does is pass the new updated element to the rendering library you provided to\nto `createMount`. It is then the library's responsibility to create the side-effect updating the DOM.\n\nNote that you can absolutely ignore the stream part and write your code without seeing a single stream. Like in Redux, every action passed to dispatcher will go through the cycle. However, using streams can give you additional control. The `mount` method returns the action stream that is both readable and writeable. That means, you can attach other subscribers to your actions, or you can actively pipe new values into it, causing the same or additional actions passed to the reducer and subsequently updating the DOM.\n\n\nA basic example below is demonstrating how the action stream can be externally driven in addition to user actions.\n\n\n## Full reactive control of your uncomponents\n\nThe `un` mount function, created as described above, \nreturns for every uncomponent, the object \n```js\nconst { \n  states: streamOfStates, \n  actions: streamOfActions \n} = mount(...)\n```\nholding the state and action streams\n(we like to refer to streams by plurals to emphasize their collection nature).\n\nThat means, you can conveniently add any complex behavior (such as loading external data)\nto your uncomponent by piping the external actions into its action stream,\nor you can attach an external subscriber to the state stream,\nto be updated on any state changes in a reactive fashion.\n\n[The active-counter example](https://github.com/dmitriz/un/tree/master/examples/active-counter) demonstrates this feature, see below.\n\n\n### `un` reactive vision\n\nRight now the streams provided by `un` conform to the [Mithril Stream API](https://mithril.js.org/stream.html)\n\nIn order to make using `un` as universal and painless as possible, \nand accessible to broader audience, we would like to facilitate plugging other stream libraries. \nSo you can use your favorite stream api to control your uncomponents.\n\n### Help and contributions are welcome!\n\n\n\n## [The active-counter example](https://github.com/dmitriz/un/tree/master/examples/active-counter)\n\n\n### Pure reducer function\n\nOur both state and action values are just numbers \nand the reducer simply adds the action value to the state:\n\n```js\nconst reducer = (state, action) =\u003e \n  state + action\n```\n\nBy making the state local and avoiding giving specific names to the actions,\nwe can make the reducer function more of a general purpose and reusable.\nJust like a function in [`Ramda`](http://ramdajs.com/) or similar library.\n\n\n### Pure view function\n\nOur view function example here demonstrates how a function helper inside\ncan reuse all the arguments of the outside function:\n\n```js\nconst view = ({ button }) =\u003e (state, dispatch) =\u003e {\n\n  // reusing the button function\n  const change = amount =\u003e \n    button( \n      {onclick: () =\u003e dispatch(amount)}, \n      (amount \u003e 0) \n        ? `+${amount}` \n        : `-${-amount}`\n      )\n\n  return [  \n    `Increasing by 5 every second: `,\n    change(10),\n    ` ${state} `,\n    change(-10)\n  ]\n}\n```\n\nHere we attach to the `onclick` listener (following `Mithril`s API flavour)\nthe anonymous function passing the `amount` to the `dispatch`. \nAs mentioned above, that value is passed as action to the reducer.\n\nBehind the scene, every time the user clicks that button,\nthe `amount` value is written into the `action` stream.\nThat is essentially what the `dispatch` function does.\n\n\n### Use with HTTP responses, Promises and other external actions\n\nIn addition to user's actions, the counter is being updated \nfrom the application via this code:\n\n```js\nconst delayedConstant = (val, delay) =\u003e stream =\u003e {\n  setInterval(() =\u003e stream(val), delay)\n  return stream\n}\ndelayedConstant(5, 1000)(actions)\n```\n\nThe `actions` stream here is exported from the `un` `mount` function\nand gives access to the external drivers.\nThe simple periodic values here are for demonstration purposes.\nThey can be replaced by any HTTP request or any value returned by a JS Promise,\nor can be even subscribed to another stream, such as event stream from any event:\n\n```js\n// the response value from the promise will appear in the actions stream\n// once the promise is resolved or the error object if the promise is rejected\nactions(fetch(someUrl))\n\n// every value from the `externalStream` \n// will be passed down the actions stream in real time\nexternalStream.map(actions)\n```\n\nThe functionality here is based on the \n[getter-setter syntax of the `flyd` stream library](https://github.com/paldepind/flyd#creating-streams) and [the way promises are treated](https://github.com/paldepind/flyd#using-promises-for-asynchronous-operations).\nHowever, any another stream library with stream updates functionality\n(e.g. [`mostjs-subject`](https://github.com/mostjs-community/subject))\ncan be used instead.\n\nOnce subscribed as above, all values will be automatically\npassed to the reducer as action values.\nThat way all business logic needed can be put \nas pure function updating the state inside the reducer.\n\nThe rest is full automatic: reducer runs on every new action,\nthe state is updated and passed to the view\nthat will be sent to the renderer to update the dom.\n\n\n### The mount function\n\n```js\n// the only method you ever use from 'un'\nconst createMount = require('un.js')\n\n// or React.createElement\n\nconst mount = createMount({ \n\n  // your favorite stream factory\n  // TODO: flyd, most, xstream\n  createStream: require(\"mithril/stream\"),\n\n  // your favorite element creator\n  // TODO: (React|Preact|Inferno).createElement, snabbdom/h, hyperscript\n  createElement: require('mithril'),\n\n  // your favorite create tags helpers\n  createTags: require('hyperscript-helpers'),\n\n  // TODO: (React|Preact|Inferno).render, snabbdom-patch, replaceWith\n  createRender: element =\u003e vnode =\u003e require('mithril').render(element, vnode)\n})\n\n// create dom element\nconst e = document.createElement('div')\ndocument.body.appendChild(e)\n\n// mount our live uncomponent and get back its writeable stream of actions\nconst actions = mount({ e, reducer, view, initState: 0 })\n```\n\n\n## More Examples\n\nThe Basic Examples are intentionally made very simple and focused.\n\n[The submit example](https://github.com/dmitriz/un/tree/master/examples/basic/submit) demonstrates how to attach an simple update action to the `onsubmit` event of the `\u003cform\u003e`.\n\n[The submit-with-reset example](https://github.com/dmitriz/un/tree/master/examples/basic/submit-with-reset) in addition resets the input field after submission by providing additional state variable to control it.\n\n[The keypress example](https://github.com/dmitriz/un/tree/master/examples/basic/keypress) demonstrates how to pass all keys pressed to the dispatcher, and then via the reducer, back to the state.\n\n[The todos example](https://github.com/dmitriz/un/tree/master/examples/todos) demonstrates a basic interactive todos collector.\n\n[The todos-with-delete example](https://github.com/dmitriz/un/tree/master/examples/todos-with-delete) adds the delete feature to the previous example. It deviates slightly from the traditional todos examples, in which the deletion is done by name, i.e. all todos with the same name are deleted at the same time.\n\n\n### More advanced examples are in active development, help and contributions are welcome!\n\n\n## Inspirations (incomplete and in random order)\n\n\u003ca href=\"https://drboolean.gitbooks.io/mostly-adequate-guide/content/\"\u003eProfessor Frisby's Mostly Adequate Guide to Functional Programming\u003c/a\u003e\nby the same author.\n\n[Professor Frisby Introduces Composable Functional JavaScript](https://egghead.io/lessons/javascript-refactoring-imperative-code-to-a-single-composed-expression-using-box)\n\n[The introduction to Reactive Programming you've been missing](https://gist.github.com/staltz/868e7e9bc2a7b8c1f754) by Andre Staltz, as well as [many other of his always enlightening posts](https://staltz.com/blog.html) as well as his [CycleJS project](https://cycle.js.org/)\n\n[@sindresorhus on reusable modules](https://github.com/sindresorhus/ama/issues/10#issuecomment-117766328)\n\nThe [Ramda](http://ramdajs.com/) and its sister [Fantasy Land](https://github.com/fantasyland/fantasy-land) and [Sanctuary](https://github.com/sanctuary-js/sanctuary) projects\n\nThe [Mithril framework](https://mithril.js.org/index.html)\n\nThe [simple but powerful `flyd` stream library](https://github.com/paldepind/flyd)\n\nThe new [Functional Reactive `Turbine` framework](https://github.com/funkia/turbine) and the oder [Functional Frontend Architecture](https://github.com/paldepind/functional-frontend-architecture)\nby @paldepind\n\nThe [Snabbdom virtual dom library](https://github.com/snabbdom/snabbdom) and the [broader Hyperscript Ecosystem](https://github.com/hyperhype/awesome-hyperscript)\n\nThe [Redux](http://redux.js.org/) and [Elm](https://guide.elm-lang.org/architecture/) projects\n\n[TodoMVC example built with `most.js`](https://github.com/briancavalier/most-todomvc)\n\n[Meiosis](https://github.com/foxdonut/meiosis)\n\n[redux-react-local](https://github.com/threepointone/redux-react-local)\n\n...To be extended...\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdmitriz%2Fun","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdmitriz%2Fun","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdmitriz%2Fun/lists"}