{"id":23094262,"url":"https://github.com/aswitalski/chromium-reactor","last_synced_at":"2026-05-07T08:38:15.662Z","repository":{"id":57198531,"uuid":"78898278","full_name":"aswitalski/chromium-reactor","owner":"aswitalski","description":"A framework for Chromium-based browsers for building their user interfaces","archived":false,"fork":false,"pushed_at":"2017-06-10T10:29:25.000Z","size":3120,"stargazers_count":2,"open_issues_count":3,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-03-09T13:46:26.032Z","etag":null,"topics":["chromium","dom","framework","javascript","ui"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/aswitalski.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-01-14T00:39:44.000Z","updated_at":"2022-05-24T02:26:00.000Z","dependencies_parsed_at":"2022-09-16T14:10:36.439Z","dependency_job_id":null,"html_url":"https://github.com/aswitalski/chromium-reactor","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aswitalski%2Fchromium-reactor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aswitalski%2Fchromium-reactor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aswitalski%2Fchromium-reactor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aswitalski%2Fchromium-reactor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aswitalski","download_url":"https://codeload.github.com/aswitalski/chromium-reactor/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247061882,"owners_count":20877176,"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":["chromium","dom","framework","javascript","ui"],"created_at":"2024-12-16T21:58:40.647Z","updated_at":"2026-05-07T08:38:15.625Z","avatar_url":"https://github.com/aswitalski.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# chromium-reactor\nA framework for Chromium-based browsers for building their user interfaces\n\n[![Build Status](https://travis-ci.org/aswitalski/chromium-reactor.svg?branch=master)](https://travis-ci.org/aswitalski/chromium-reactor)\n[![Coverage Status](https://coveralls.io/repos/github/aswitalski/chromium-reactor/badge.svg?branch=master)](https://coveralls.io/github/aswitalski/chromium-reactor?branch=master)\n[![npm version](https://img.shields.io/npm/v/chromium-reactor.svg?style=flat)](https://www.npmjs.com/package/chromium-reactor)\n\n## What is it?\n\nChromium Reactor is a framework intended for building Web UIs for Chromium-based browsers.\nIt utilises the engine’s latest features and provides a convenient way to build native, modular and dynamic web apps.\n\n## Design principles\n\n* **native** - take advantage of the latest Chromium engine features,\n* **modular** - define each component, reducer, service as a separate module,\n* **dynamic** - build in discovery service, lazy-load modules for flexibility or preload for performance,\n* **fast** - utilise virtual DOM, minimise the number of DOM modifications, benchmark all operations to ensure high performance,\n* **simple** - no millions of callbacks and events, utilise one-way model-to-view binding and unidirectional data flow,\n* **encapsulated** - isolate apps, reduce usage of global variables to bare minimum,\n* **deterministic** - do not worry about race conditions, let the framework control the asynchronous operations properly,\n* **testable** - unit test all your components with little effort,\n* **debuggable** - easily inspect your apps, use live reload, instrumentation and time saving debug tools.\n\n## UI first\n\nBuilding user interfaces for a browser requires pretty much two things: a mechanism to render the UI in the DOM and the way to load and manipulate the data utilised by that UI.\n\nReactor builds the UI as an sandboxed app that renders DOM elements in the specified container.\n\nAs a rule of thumb, no excess resources are fetched unless they are needed to render the requested interface.\nDependencies required for showing particular UI fragments are defined within the components responsible for rendering those fragments. All dependencies are resolved with the built-in discovery service and module loader.\n\nMultiple apps can be rendered on the same page. They also can share dependencies, as these are stateless by design. All stateful components are encapsulated within apps.\n\n## Running an app\nThe creation and execution of apps is as simple as possible:\n\n```js\n// import app definition\nconst MyApp = loader.symbol('apps/my-app');\n// create new app\nconst app = Reactor.create(MyApp);\n// render in body element\nawait app.render(document.body);\n```\n\n## Behind the scenes\n\nCreation of an app is synchronous, it is instantiated together with all its internal components (store, core reducer, renderer). Definitions of components are loaded.\n\nThe initialisation is asynchronous and it can be triggered manually or automatically before the initial rendering.\nDuring the initialisation all the required dependencies are resolved, the view model is created and fed with the initial state provided by the data services.\n\nTe rendering cycle starts with the construction of the component tree. The component tree is based on the initial state and then used to create the virtual DOM, which gets mirrored as DOM element tree and inserted into the specified container. Event listeners are bound to the app's command dispatcher.\n\nFrom this point forward background data changes and user actions result in commands dispatched to the app.\nThe app processes the commands with the defined set of reducers, which calculate the transition to the next state. Each state update triggers the next rendering cycle.\n\n## Dynamic nature\n\nBefore anything is shown to the user, Reactor loads all modules that are required for the initial rendering to start.\nAll optional dependencies can be lazy-loaded in order to minimise the memory usage and can be discovered just-in-time while traversing the component tree during rendering. The rendering begins with the root component which defines the app itself. Its `render()` method is invoked to get a template defining the node structure. The template is calculated using the component properties (application state for the root component). It consists of static elements (like 'div' and 'span') and definitions of subcomponents.\n\n```js\nconst Application = class extends Reactor.Component {\n  render() {\n    return [\n      'div', {\n        class: 'my-app'\n      },\n      [\n        'h1', this.props.title      \n      ],\n      [\n        NavigationMenu, this.props.navigation\n      ]\n    ];\n  }\n};\n\nconst NavigationMenu = loader.symbol('/components/navigation/menu');\n```\nIf any subcomponent definitions are found in the returned template the subcomponent classes are loaded together with their dependencies. The subcomponent instances are created and they receive their own properties (as specified by the parent component) and the rendering continues:\n\n```js\nconst NavigationMenu = class extends Reactor.Component {\n  render() {\n    return [\n      'div', {\n        class: 'navigation'\n      }, ...this.props.items.map(item =\u003e [\n        NavigationItem, { label: item.label, url: item.url }\n      ])\n    ];\n  }\n};\n\nconst NavigationItem = loader.symbol('/components/navigation/item');\n```\n\nBy design components only receive properties they are interested in, usually fragments of the application state.\nAll the properties on static elements are converted into event listeners and element attributes (based on the built-in dictionaries):\n\n```js\nconst NavigationItem = class extends Reactor.Component {\n  render() {\n    return [\n      'a', {\n        class: 'item',\n        href: this.props.url\n      }, this.props.label\n    ];\n  }\n};\n```\n\n## Performance\n\nLazy loading and multiple asynchronous operations can significantly impact the app's responsiveness, therefore any component tree fragment (with all related modules) can be preloaded at any point in time. This allows to synchronously render the particular element tree to get the best performance when needed.\n\nPreloading is very handy when a switch to a totally different view happens, what usually requires a different set of components and services. There only is a single expensive operation and after that all updates triggered by user actions are optimised and as performant as possible.\n\nBoth virtual DOM and the renderer perform several optimisations to minimise the number of reflows and repaints. Components are not re-rendered if they receive exactly the same properties and child nodes as in the previous cycle. DOM is not modified if the structure and attributes remain the same and only event listeners change on the components. Preloaded components and all the descendants update synchronously in a single stack frame.\n\n## Modules\n\nApps needs to read and write data, render the user interface and process the commands representing user actions and data changes. There are three main module types needed to develop a fully fledged app:\n\n**Components** - represent UI fragments, define what is rendered in the DOM\n```js\nconst Component = class extends Reactor.Component {\n  render() {\n    return [\n      'div', {\n        class: 'main'\n      },\n      [\n        Subcomponent, {\n          onClick: this.animate()\n        }\n      ]\n    ];\n  }\n};\n\nconst Subcomponent = loader.symbol('/components/subcomponent');\n```\n**Reducers** - process commands to update the application state, but also provide an API for creation of commands that they can understand\n```js\nconst VALUE_CHANGED = Symbol('value-changed')\n\nconst reducer = (state, command) {\n  switch (command.type) {\n    case VALUE_CHANGED:\n      return {\n        settings: Object.assign({}, state.settings, {\n          [command.name]: command.value\n        });\n      };\n    default:\n      return state;\n  }\n};\n\nreducer.commands = {\n  valueChanged: (name, value) =\u003e ({\n    type: VALUE_CHANGED,\n    name,\n    value\n  })\n};\n```\n**Services** - read and write data, allow to subscribe to data changes\n```js\nconst service = class Service {\n  async getState() {\n    return await this.getSettings().toState();\n  }\n  subscribe(reducer) {\n    this.onSettingChange((name, value) =\u003e {\n      const command = reducer.commands.valueChanged(name, value);\n      reducer(command);\n    });\n  }\n}\n```\n\n## Bundling apps\n\nAll the app's dependencies are defined within the components it consists of. This allows to reuse the discovery service and module loader's mechanisms at build time to bundle up the all the required modules into production-ready files.\n\nBunding doesn't use any transpilation or source maps and does nothing impacting the readability of the code. The original formatting is maintained. The only necessary amendment in the code is a change of the path-agnostic CommonJS pattern `module.exports = App` declarations into `define('/components/app', App)` to be able to package multiple modules from different paths into a single file.\n\nIt's up to the developers how they want to bundle their apps, but there are several built-in strategies: generating a single script for the whole app, splitting the code by top-level view or bundling UI components and services separately.\n\nTo bundle your app from command line with default settings just run:\n```\nnode run bundle-app /components/my-app\n```\nApps using bundled scripts in production don't loose their dynamic nature, service discovery and lazy loading can still be used if needed.\n\n## Testing\n\nIt is much easier to ensure high quality of developed apps if all of their components are fully testable.\nAnd of course if the framework they are built with is created from ground up with convenient testing in mind.\n\nReactor simplifies unit testing by providing a dedicated module loader which automatically mocks dependencies of tested modules. UI components can also use shallow render mode not to rely on their subcomponents.\n\nThe built-in assertion library allows to avoid boilerplate code in tests and improves their readability:\n```js\n// given\nconst component = createComponent('/components/panel');\ncomponent.props = {\n  active: true\n};\n\n// when\nconst template = component.render();\nconst node = shallowRender(template);\n\n// then\nassert(node.is('div'));\nassert(node.hasClass('active'));\nassert(node.hasChildren());\nassert(node.getFirstChild().is('span'));\n```\n\n# Coming when it's done!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faswitalski%2Fchromium-reactor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faswitalski%2Fchromium-reactor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faswitalski%2Fchromium-reactor/lists"}