Repository: brahmosjs/brahmos Branch: master Commit: e3bffc1f27b1 Files: 131 Total size: 539.0 KB Directory structure: gitextract_ah3xfmck/ ├── .editorconfig ├── .eslintrc ├── .flowconfig ├── .gitignore ├── .npmignore ├── .prettierrc ├── .travis.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── babel.config.js ├── bundlesize.config.json ├── docs/ │ └── HOW_IT_WORKS.md ├── example/ │ ├── App.js │ ├── App.scss │ ├── Logos.js │ ├── common/ │ │ └── ReactCredit.js │ ├── concurrent-mode/ │ │ ├── fakeApi.js │ │ └── index.js │ ├── context-api/ │ │ └── index.js │ ├── error-boundaries/ │ │ └── index.js │ ├── food-app/ │ │ ├── food-app.css │ │ ├── food-menu.json │ │ ├── mobx/ │ │ │ ├── App.js │ │ │ ├── Comps.js │ │ │ ├── index.js │ │ │ └── mobx.js │ │ ├── react-query/ │ │ │ ├── App.js │ │ │ ├── Comps.js │ │ │ ├── hooks.js │ │ │ ├── index.js │ │ │ └── zustand.js │ │ ├── redux/ │ │ │ ├── App.js │ │ │ ├── Comps.js │ │ │ ├── index.js │ │ │ └── redux.js │ │ └── utils.js │ ├── hooks/ │ │ └── index.js │ ├── index.html │ ├── index.js │ ├── lazy-component/ │ │ └── index.js │ ├── portals/ │ │ └── index.js │ ├── sierpinski-triangle/ │ │ └── index.js │ ├── suspense-list/ │ │ ├── fakeApi.js │ │ └── index.js │ ├── svg-chart/ │ │ └── index.js │ ├── third-party-component/ │ │ └── index.js │ ├── time-slicing/ │ │ ├── Chart.js │ │ ├── Clock.js │ │ └── index.js │ ├── todo-list/ │ │ └── index.js │ ├── use-deferred-value/ │ │ ├── MySlowList.js │ │ └── index.js │ └── use-deferred-value-suspense/ │ ├── fakeApi.js │ └── index.js ├── example_old/ │ ├── App.js │ ├── ErrorBoundaryExample.js │ ├── RefsExample.js │ ├── SVGExample.js │ ├── TodoList.js │ ├── UnMountAtNode.js │ ├── UseStateExample.js │ ├── concurrentApp.js │ ├── context.js │ ├── createPortalExample.js │ ├── data.json │ ├── fakeApi.js │ ├── fakeApiSuspenseList.js │ ├── friends.json │ ├── index.html │ ├── index.js │ ├── lazySuspenseExample.js │ ├── suspenseExamples.js │ └── suspenseListExample.js ├── jest.config.js ├── package.json ├── rollup.config.js ├── src/ │ ├── Children.js │ ├── Component.js │ ├── Suspense.js │ ├── TagNode.js │ ├── TemplateNode.js │ ├── TemplateTag.js │ ├── __tests__/ │ │ ├── BrahmosES6class.test.js │ │ ├── BrahmosPureComponent.test.js │ │ ├── __snapshots__/ │ │ │ └── listRendering.test.js.snap │ │ ├── attributes.test.js │ │ ├── children_apis.test.js │ │ ├── context.test.js │ │ ├── jest.setup.js │ │ ├── listRendering.test.js │ │ ├── memo.test.js │ │ ├── setState.test.js │ │ ├── testUtils.js │ │ └── utils.test.js │ ├── brahmos.js │ ├── brahmosNode.js │ ├── circularDep.js │ ├── configs.js │ ├── createContext.js │ ├── createElement.js │ ├── createPortal.js │ ├── effectLoop.js │ ├── fiber.js │ ├── flow.types.js │ ├── functionalComponentInstance.js │ ├── helpers/ │ │ └── shallowEqual.js │ ├── hooks.js │ ├── index.js │ ├── memo.js │ ├── parseChildren.js │ ├── processArrayFiber.js │ ├── processComponentFiber.js │ ├── processTagFiber.js │ ├── processTextFiber.js │ ├── reRender.js │ ├── reactEvents.js │ ├── refs.js │ ├── render.js │ ├── scheduler.js │ ├── tags.js │ ├── tearDown.js │ ├── transitionUtils.js │ ├── unmountComponentAtNode.js │ ├── updateAttribute.js │ ├── updateUtils.js │ ├── utils.js │ └── workLoop.js ├── tsconfig.json └── webpack.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # EditorConfig is awesome: http://EditorConfig.org root = true [*] indent_size = 2 charset = utf-8 end_of_line = lf indent_style = space insert_final_newline = true trim_trailing_whitespace = true [*.md] trim_trailing_whitespace = false ================================================ FILE: .eslintrc ================================================ { "env": { "browser": true, "jest/globals": true }, "parser": "babel-eslint", "extends": ["standard", "prettier"], "plugins": ["flowtype", "jest"], "rules": { "comma-dangle": ["error", "always-multiline"], "semi": ["error", "always"], "flowtype/define-flow-type": 1 } } ================================================ FILE: .flowconfig ================================================ [ignore] [include] [libs] [lints] [options] [strict] ================================================ FILE: .gitignore ================================================ node_modules .cache dist lib *.log reference .DS_Store # editor specific files .vscode .package-lock.json ================================================ FILE: .npmignore ================================================ # Ignore source and reference files src node_modules reference # Ignore config files .eslintrc .gitignore webpack.config.js # Ignore doc and example file docs example example_old ================================================ FILE: .prettierrc ================================================ { "printWidth": 100, "arrowParens": "always", "singleQuote": true, "tabWidth": 2, "useTabs": false, "semi": true, "bracketSpacing": true, "jsxBracketSameLine": false, "requirePragma": false, "proseWrap": "preserve", "trailingComma": "all" } ================================================ FILE: .travis.yml ================================================ language: node_js node_js: - "8" script: - echo 'Starting Build!' - echo 'Installing Deps!' - yarn - echo 'Testing!' - yarn test - echo 'Checking Bundle Size!' - yarn test:bundlesize deploy: on: tags: true provider: npm email: "$EMAIL_ADDRESS" api_key: "$AUTH_TOKEN" ================================================ FILE: CHANGELOG.md ================================================ v11.0 - Support new jsx transform. - Children APIs - 3rd Party React library support (Tested React-router, redux, mobx, react-query, zustand, recharts) - Added couple of unit test for the library. v10.0 - Concurrent mode API support - Suspense for data fetch, Suspense. - React Memo v9.0 - Rewrote library with Fiber architecture. v8.0 - SVG support - Portals - Context API ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at sudhanshuyadav2@gmail.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2019 Sudhanshu Yadav Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

Brahmos.js

# Brahmos Supercharged JavaScript library to build user interfaces with modern React API and native templates. Brahmos supports all the APIs of React including the upcoming concurrent mode APIs and the existing ones. It has its own custom fiber architecture and concurrent mode implementation to support the concurrent UI patterns. ## Features - Lightweight and Fast. - Exact same React's Declarative APIs with JSX. - Fast alternative to Virtual DOM. (JSX without VDOM). - Smaller transpiled footprint of your source code, than traditional JSX. ## Installation ### Create Brahmos App Use [Create a New Brahmos App](https://www.npmjs.com/package/create-brahmos-app) if you're looking for a powerful JavaScript toolchain. ### Manual installation Add `brahmos` as dependency. And `babel-plugin-brahmos` as dev dependency. ``` npm install brahmos npm install babel-plugin-brahmos --save-dev ``` Add brahmos in your babel config. ``` { presets: ['@babel/preset-env'], plugins: [ //... 'brahmos' ] } ``` **Note:** You will have to remove react preset from babel if you trying brahmos on existing project. ## Usage The API is exact same as React so build how you build application with React, but instead of importing from `react` or `react-dom` import from `brahmos`; ```js import {useState, useEffect} from 'brahmos'; export default function App(props) { const [state, setState] = useState(0); return (
...
) } ``` ### Using React 3rd party libraries Just alias react and react-dom with brahmos. And you are good to go using 3rd party react libraries. You need to add following aliases. ```js alias: { react: 'brahmos', 'react-dom': 'brahmos', 'react/jsx-runtime': 'brahmos' }, ``` - **webpack** ([https://webpack.js.org/configuration/resolve/#resolvealias](https://webpack.js.org/configuration/resolve/#resolvealias)) - **parcel** ([https://parceljs.org/module_resolution.html#aliases](https://parceljs.org/module_resolution.html#aliases)) - **rollup** ([https://www.npmjs.com/package/@rollup/plugin-alias](https://www.npmjs.com/package/@rollup/plugin-alias)) - **babel** ([https://www.npmjs.com/package/babel-plugin-module-resolver](https://www.npmjs.com/package/babel-plugin-module-resolver)) ## Idea It is inspired by the rendering patterns used on hyperHTML and lit-html. It has the same declarative API like React, but instead of working with VDOM, it uses tagged template literals and HTML's template tag for faster rendering and updates. It divides the HTML to be rendered into static and dynamic parts, and in next render, it has to compare the values of only dynamic parts and apply the changes optimally to the connected DOM. It's unlike the VDOM which compares the whole last rendered VDOM to the new VDOM (which has both static and dynamic parts) to derive the optimal changes that are required on the actual DOM. Even though tagged template literals are the key to static and dynamic part separation, the developer has to code on well adopted JSX. Using the babel-plugin-brahmos it transforms JSX into tagged template literals which are optimized for render/updates and the output size. Consider this example, ```jsx class TodoList extends Component { state = { todos: [], text: '' }; setText = (e) => { this.setState({ text: e.target.value }); }; addTodo = () => { let { todos, text } = this.state; this.setState({ todos: todos.concat(text), text: '', }); }; render() { const { todos, text } = this.state; return (
); } } ``` It will be transformed to ```js class TodoList extends Component { state = { todos: [], text: '' }; setText = (e) => { this.setState({ text: e.target.value }); }; addTodo = () => { let { todos, text } = this.state; this.setState({ todos: todos.concat(text), text: '', }); }; render() { const { todos, text } = this.state; return html`
`("0|0|1,0|1|0,1|3|"); } } ``` With the tagged template literal we get a clear separating of the static and dynamic part. And on updates it needs to apply changes only on the changed dynamic parts. Tagged template literals also have a unique property where the reference of the literal part (array of static strings) remain the same for every call of that tag with a given template. Taking advantage of this behavior Brahmos uses literal parts as a cache key to keep the intermediate states to avoid the work done to process a template literal again. Tagged template is natively supported by the browser, unlike the React's JSX which has to be transformed to React.createElement calls. So the output generated to run Brahmos has a smaller footprint than the output generated for the react. For the above example, the Brahmos output is 685 bytes, compared to 824 bytes from the React output. More the static part of an HTML, greater the difference will be. ## Demo The following demo demonstrates the support of all the APIs coming in future version of React like Concurrent mode, suspense list, suspense for data fetch, and also for the existing APIs like states, hooks, context api, refs etc. [https://codesandbox.io/s/brahmos-demo-3t8r6](https://codesandbox.io/s/brahmos-demo-3t8r6) ## Talk on the Idea of Brahmos Brahmos.js: React without VDOM ## Slack Channel https://join.slack.com/t/brahmoscommunity/shared_invite/enQtODM5NDMwODgwMzQyLTc4YjJlZjY3Mzk1ODJkNTRkODljYjhmM2NhMGIxNzFjMjZjODk0MmVjZTVkNmE5Y2MwYzZkMzk5NTUxYmI5OWE ## Progress - [x] Babel Plugin to transpile JSX to tagged template - [x] Class components with all life cycle methods (Except deprecated methods) - [x] Functional Component - [x] List and Keyed list - [x] Synthetic input events - onChange support - [x] Hooks - [x] Context API - [x] Refs Api, createRef, ref as callback, forwardRef - [x] SVG Support - [x] Suspense, Lazy, Suspense for data fetch, Suspense List - [x] Concurrent Mode - [x] 3rd Party React library support (Tested React-router, redux, mobx, react-query, zustand, recharts) - [x] React Utilities and Methods - [ ] Handle server rendering - [ ] Performance improvement - [ ] Bug fixes - [ ] Test Cases ================================================ FILE: babel.config.js ================================================ module.exports = { presets: [['@babel/preset-env']], plugins: [ '@babel/plugin-transform-flow-strip-types', '@babel/plugin-transform-runtime', '@babel/plugin-proposal-class-properties', ['@babel/plugin-proposal-object-rest-spread', {}, 'rest-spread'], 'brahmos', ], }; ================================================ FILE: bundlesize.config.json ================================================ { "files": [ { "path": "./dist/brahmos.js", "maxSize": "20 kB" }, { "path": "./dist/brahmos.min.js", "maxSize": "7 kB" } ] } ================================================ FILE: docs/HOW_IT_WORKS.md ================================================

⚠️ Warning: This document is stale, a lot has been changed since this doc was published. This need's to be updated.

This document is to explain how Brahmos work, its concept, internals and taxonomies. This is a good place for the contributors and people who want to understand the inner working of the Brahmos. This doc tries to cover the different parts of Brahmos and explains what they do. Links to the source files are also added so you can dive deep through code and comments as well. This doc tries to be as much as descriptive so that you can understand the internals of Brahmos. But if things are unclear and you have some ideas to improve it, please raise an issue or submit a PR. - [Concept](#concept) - [Brahmos Transformation (with babel-plugin-brahmos)](#brahmos-transformation-with-babel-plugin-brahmos) - [Basic html with dynamic value](#basic-html-with-dynamic-value) - [Html with Custom Component](#html-with-custom-component) - [More complex transformation](#more-complex-transformation) - [Tag function `html`](#tag-function-html) - [createElement](#createelement) - [Brahmos Nodes](#brahmos-nodes) - [Brahmos Tag Node](#brahmos-tag-node) - [Brahmos Component Node](#brahmos-component-node) - [Brahmos Element Node](#brahmos-element-node) - [Brahmos String Node](#brahmos-string-node) - [Brahmos Array Node](#brahmos-array-node) - [Brahmos node tree](#brahmos-node-tree) - [TemplateTag](#templatetag) - [TemplateNode](#templatenode) - [Class Component](#class-component) - [Functional Component](#functional-component) - [Associating instance](#associating-instance) - [Updater](#updater) - [Attribute updater](#attribute-updater) - [Node updater](#node-updater) - [Non renderable node](#non-renderable-node) - [Component node](#component-node) - [Tag node & Element node](#tag-node--element-node) - [Array nodes](#array-nodes) - [Text nodes](#text-nodes) - [Tear down](#tear-down) ## Concept Brahmos is a lightweight implementation of React without VDOM. It has the same declarative API like React, but instead of working with VDOM, it uses tagged template literals and HTML's template tag for faster rendering and updates. It divides the HTML to be rendered into static and dynamic parts, and in next render, it has to compare the values of only dynamic parts and apply the changes optimally to the connected DOM. It's unlike the VDOM which compares the whole last rendered VDOM (which has both static and dynamic parts) with the new VDOM to derive the optimal changes that are required on the actual DOM. It has the exact same API as React but the Brahmos compiler and the renderer makes this library different. Let's see how it works. ## Brahmos Transformation (with babel-plugin-brahmos) A developer writes code in JSX and the babel-plugin-brahmos transform code to templateLiteral. Here are example of few transformation. #### Basic html with dynamic value ```jsx
Hello {name} !!
``` In Brahmos this will be transformed to. ```js html`
Hello ${name} !!
`; ``` Compared to React ```js React.createElement( 'div', { className: 'greet', }, 'Hello ', React.createElement( 'span', { className: 'name', }, name, ), ' !!', ); ``` With the string literal, you can see there is a clear separation between static and dynamic part. #### Html with Custom Component ```jsx
Hello !!
``` Brahmos: ```js html`
Hello ${createElement(Name, { value: 'name' })} !!
`; ``` React: ```js React.createElement( 'div', { className: 'greet', }, 'Hello ', React.createElement(Name, { value: name, }), ' !!', ); ``` - For Custom component it uses the syntax createElement similar to React as they cannot be converted to the template tag. With the first argument being a Component reference, the 2nd argument being props followed by children. - The output of createElement of Brahmos is slightly different than the React. - This also enables support for third-party React component to be used along with Brahmos. #### More complex transformation ```jsx
{itemShortDesc}

{itemDesc}

``` Brahmos: ```js html`
${createElement( Item, {}, html`

${itemDesc}

`, )}
`; ``` React: ```js React.createElement( 'div', { className: 'items', }, React.createElement( Item, null, React.createElement('img', { className: 'item-img', src: itemImg, alt: itemShortDesc, }), React.createElement('p', null, itemDesc), React.createElement( 'button', { className: 'add-item', onClick: (void 0).addItem, }, 'Add', ), ), ); ``` - Dynamic attributes are transformed into key-value pairs while also maintaining the node reference of that dynamic attributes. - A tagged template literal can be passed as children of createElement call. Children can be string, tagged component literal, another custom component or array of all this. - We can also see how the overall footprint of Brahmos output is smaller than the JSX output. And bigger the static part bigger the difference will be more. ## Tag function `html` Source: [https://github.com/s-yadav/brahmos/blob/master/src/tags.js](https://github.com/s-yadav/brahmos/blob/master/src/tags.js) On the code transformed by Brahmos, we see the ES6 template literal with a tag. Tags are nothing but a function which receives an array of the literal part (static part) followed by all expressions (values). ```js function html(strings, ...values) { // string ['
\nHello ', ' !!\n
'] // values = [name] } ``` A tag function doesn't have to return only the string, it can return anything. The html tag function returns an object (Brahmos Tag Node) which contains TemplateTag instance (which we will see later) and an array of dynamic expressions. Brahmos Tag Node ```js { template: templateTagInstance, values: [name], } ``` Also on a tag function, the reference of string array remains the same for a given template. We use this behaviour to cache the templateTagInstance for a given literal (static) part and reuse it for next calls. ## createElement Source: https://github.com/s-yadav/brahmos/blob/master/src/createElement.js createElement API is similar to react where the first argument is an element (component reference or a string), the second argument is for props followed by children. It returns the Brahmos element node if a string is passed as the first argument and returns the Brahmos component node if component reference is passed. Brahmos Element Node ```js { element: 'div', values: [attributes, children] } ``` Brahmos Component Node ```js { type: BrahmosComponent, props: { ...allPassedProps, children, } key, ref, } ``` Similar to React children are passed as a prop. key and ref are reserved props and are not passed to the component. ## Brahmos Nodes Brahmos has different types of nodes which are handled and updated differently on first render or updates. #### Brahmos Tag Node This is returned by tagged template, which looks like ```js { template: templateTagInstance, values: [...dynamicExpressions], __$isBrahmosTag$__: true, } ``` #### Brahmos Component Node - Brahmos component node is further divided into `Class Component Node` and `Functional Component Node`. - Component nodes are returned by the createElement call with custom component. ```js { type: BrahmosComponent, props: { ...allPassedProps, children, } key, ref, __$isBrahmosComponent$__: true, __$isBrahmosClassComponent$__: true | false, __$isBrahmosFunctionalComponent$__: true | false, } ``` #### Brahmos Element Node - This is returned by the createElement call with HTML elements tagName instead of custom component. ```js { element: 'div', values: [attributes, children], __$isBrahmosTag$__: true, __$isBrahmosTagElement$__: true, } ``` #### Brahmos String Node - This is a simple string and is rendered as a text node. #### Brahmos Array Node - This is an array of the above types of nodes. ## Brahmos node tree Based on the different types of nodes Brahmos forms a node tree. Unlike the VDOM which is a tree representation of the actual dom, Brahmos maintains a tree of Brahmos nodes. So the depth of the Brahmos node is much smaller than VDOM. For the following JSX ```jsx
{itemShortDesc}

{itemDesc}

``` Brahmos tree will look like ```js { template: templateTagInstance, // for the outer div values: [{ type: Item, props: { children: { template: templateTagInstance, // for the Item content values: [itemImg, itemShortDesc, itemDesc, addItem] } }, __$isBrahmosComponent$__: true, }] __$isBrahmosTag$__: true, } ``` Compared to React VDOM tree which looks like ```js { type: 'div', props: { className: 'items', children: [{ type: 'item', props: { children: [{ type: 'img' , props: { className: 'item-img', src: itemImg, alt: itemShortDesc, } }, { type: 'p', props: { children: [itemDesc] }, }, { type: 'button', props: { className: 'add-item', onClick: addItem, children: ['Add'] } }] } }] } } ``` The Brahmos tree is then recursively analyzed to associate proper node instances and to render. ## TemplateTag Source: https://github.com/s-yadav/brahmos/blob/master/src/TemplateTag.js TemplateTag class has two responsibilities. 1. Create a template tag (HTML tag) for a given literal part (static part). 2. Create basic parts metadata. For a given template ```js html`
Hello ${name} !!
`(); ``` TemplateTag Class will create a template tag which will look like ```html ``` It adds placeholder for attribute part and node part so parts can be processed easily later. It will also create dynamic part metadata. For the above template this will look like ```js [ { tagAttrs: ['class'], //this are all other attributes other than dynamic attributes. attrIndex: 1, // store the location of dynamic attribute part isAttribute: true, }, { isNode: true, }, ]; ``` - For the tag attribute, we store the index (location) of dynamic attribute so attribute can be applied in order without overriding attributes which it shouldn't. For example ```js html`
``` This should have `someOtherId` as id. While ```js html`
``` This should have `someId` as id. attrIndex helps to figure out which one to apply in such cases. creating a template tag and part metadata is deferred until the first render of that component. This is required as some information is available during the render time only like whether a template is part of an SVG or the normal HTML. ## TemplateNode Source: https://github.com/s-yadav/brahmos/blob/master/src/TemplateNode.js TemplateNode class do two things 1. Create DOM nodes from the template tag removing the placeholders. 2. Create part information using placeholder location and parts metadata from the templateTagInstance. Each of Brahmos tag node has different TemplateNode instance, but it remains the same for all consecutive renders till it has same TemplateTag instance. If TemplateTag instance does not have template tag and part metadata created, TemplateNode initializes that on TemplateTag Instance. TemplateNode then clones the template tag and remove place holders and creates the part information which has actual DOM references of dynamic locations. Node which is appended to dom ```html
Hello !!
``` Part information ```js [{ tagAttrs: ['class'], attrIndex: 1, isAttribute: true, node: Element, //div.greeting }, { isNode: true, parentNode: Element //span.name previousSibling: null, //null because there is no previousSibling of the dynamic part nextSibling: null, // null because there is no nextSibling of the the dynamic part }] ``` Now with this part information, dynamic parts can be updated directly as now we know the actual DOM locations for those dynamic parts. On creation of TemplateNode instance, it attaches this instance to Brahmos tag node so it can be used later. ```js { template: templateTagInstance, values: [...dynamicExpressions], templateNode: templateNodeInstance, __$isBrahmosTag$__: true, } ``` ## Class Component Source: https://github.com/s-yadav/brahmos/blob/master/src/Component.js Class Component API is exactly the same as the React. There are few implementation changes though from the React. - The deprecated life cycle methods are not supported. Even unsafe life cycle methods are not supported. - setState calls batch all the state change and are always asynchronous. Class Component keeps the last rendered node information so on next render it can associate proper instance to each of Brahmos Node. During the render, the updater creates ComponentInstance and attaches it to the Brahmos component node. ```js { type: Component, props: {...} componentInstance: classComponentInstance, } ``` ## Functional Component Source: https://github.com/s-yadav/brahmos/blob/master/src/functionalComponentInstance.js Functional Component is written the same way as React, While rendering Brahmos creates an instance of a functional component and associate the instance to Brahmos Component node. ```js { type: Component, props: {...} componentInstance: functionalComponentInstance, } ``` ## Associating instance Source: https://github.com/s-yadav/brahmos/blob/master/src/associateInstance.js This is key to making optimal updates. On rerender of a Brahmos node, the associateInstance method associate already created templateNodeInstance, or componentInstance to the new node, so it gets the reference of dynamic parts on the actual dom node. If a node does not get an associated instance it means it is a new node and old node needs to be removed and the new one should get added. The instance association to the new node happens with following rules. - If the new node and old node at a specific tree path are different types, then return without associating anything. - If the new node is a Brahmos tag node, and the templateTag instance of new node and old node is same then associate the templateNode instance to the new node. (If the literal part of a template is same, the templateTag instance will always remain same) - If the new node is a Brahmos component node and the node.type matches with oldNode.type then it associates component instance form the old node to the new node. - If the new node is Brahmos element node, and the element tagName matches with the oldNode's tagName then it associates templateNode instance to the new node. - If the new node is an array, each of the nodes in that array is matched with the item in the old node having the same index or the same key. And if they match based on the above condition instance are associated with the node. ## Updater Source: https://github.com/s-yadav/brahmos/blob/master/src/updater.js An updater is a place where all rendering or updates happens. It recursively updates all the Brahmos node on the tree. There are three main arguments of an updater. - parts, which is an array of dynamic part and have information about DOM location and type of part (attribute or node). - values, which is an array of dynamic values. - oldValues, an array of old values against which the updater can compare and only if there is a change in value apply the changes to the DOM. There are different types of updates with two categories. - Attribute updater - Node updater Apart from calling proper updater it takes care of two more thing - Merge all dynamic attribute for a dom node, so it can be effectively updated. - Set proper ref if ref prop is passed to an element. ### Attribute updater Source: https://github.com/s-yadav/brahmos/blob/master/src/updateAttribute.js - Attribute updater takes care of updating the attribute of a given node based on the index of that attribute. This information comes from the part object. - It handles dom node property and attributes differently. - For event properties, it takes care of removing old event listeners (if the eventListener is changed) and add new event listeners. - To be React compatible it has synthetic events only for inputs. It has the same onChange API as react. https://github.com/s-yadav/brahmos/blob/master/src/reactEvents.js - For inputs, it takes care of controlled and uncontrolled input similar to React. https://github.com/s-yadav/brahmos/blob/master/src/reactEvents.js ### Node updater Source: https://github.com/s-yadav/brahmos/blob/master/src/updateNode.js Node updater handles rendering and updates of different type of Brahmos node. #### Non renderable node For non-renderable values like `null`, `undefined`, `false`, it bails out and remove the existing old node if present on that location. #### Component node Source: https://github.com/s-yadav/brahmos/blob/master/src/updateComponentNode.js `updateComponentNode` handles updating a component node. It creates a component instance and associates the instance to the component node if it's not already created. For class components - it calls all the applicable life cycle methods if provided and renders with the error boundaries. - It applies and flushes all the uncommitted state if present for that component. - It also takes care or providing proper context to a component and merge contextValue provided by the ContextProvider for the component nodes down the tree. - It sets a proper component reference to ref prop. For functional component - It takes care of calling effects after the render and clean the effects before the next render. #### Tag node & Element node Source: https://github.com/s-yadav/brahmos/blob/master/src/updateNode.js#L135 - It creates a TemplateNode instance and associates it with Brahmos tag node if it's not already created. - For the element node, it creates an object similar to templateNode (if not created already) and associates it to the node. https://github.com/s-yadav/brahmos/blob/master/src/TagNode.js - For the first render, it adds everything on a document fragment and adds it to DOM at the end to avoid multiple reflows. #### Array nodes Source: https://github.com/s-yadav/brahmos/blob/master/src/updateNode.js#L91 - Array's are optimally updated based on provided keys. It reuses the same DOM element even if the order changes (if proper keys are provided). - It takes care of optimally removing unused old nodes, reordering of nodes and adding new nodes. #### Text nodes Source: https://github.com/s-yadav/brahmos/blob/master/src/updateNode.js#L23 - For text nodes, it finds the existing text node at the DOM tree path and only updates the text value avoiding creating new text node. ## Tear down Source: https://github.com/s-yadav/brahmos/blob/master/src/tearDown.js Tear down is a way to recursively delete a path from a given node on the Brahmos tree. It takes care of the following things. - Unmount the components and call life cycle methods for the class component. - Clean the effects on a functional component. - Unset the refs. - Delete the dom nodes. ================================================ FILE: example/App.js ================================================ import { useState, useEffect, useMemo } from 'brahmos'; import { Switch, Route, NavLink, useLocation } from 'react-router-dom'; import { BrahmosLogo, GithubLogo } from './Logos'; import TodoList from './todo-list'; import ConcurrentModeDemo from './concurrent-mode'; import SuspenseListDemo from './suspense-list'; import UseDeferredValueDemo from './use-deferred-value'; import UseDeferredValueSuspenseDemo from './use-deferred-value-suspense'; import LazyComponentDemo from './lazy-component'; import PortalDemo from './portals'; import ErrorBoundariesDemo from './error-boundaries'; import SVGDemo from './svg-chart'; import HooksDemo from './hooks'; import ContextDemo from './context-api'; import RechartExample from './third-party-component'; import ReduxExample from './food-app/redux'; import MobxExample from './food-app/mobx'; import ReactQueryExample from './food-app/react-query'; import './App.scss'; const examples = [ { title: 'TODO List', id: 'todo-list', Component: TodoList, }, { title: 'Context API', id: 'context-api', Component: ContextDemo, }, { title: 'Hooks Demo', id: 'hooks', Component: HooksDemo, }, { title: 'Error Boundaries Demo', id: 'error-boundaries', Component: ErrorBoundariesDemo, }, { title: 'SVG Support Demo', id: 'svg-support', Component: SVGDemo, }, { title: 'Portal Demo', id: 'portals', Component: PortalDemo, }, { title: 'Concurrent Mode Demo', id: 'concurrent-mode', Component: ConcurrentModeDemo, }, { title: 'Suspense List Demo', id: 'suspense-list', Component: SuspenseListDemo, }, { title: 'Suspense with useDeferredValue', id: 'use-deferred-value-suspense', Component: UseDeferredValueSuspenseDemo, }, { title: 'Time slicing with useDeferredValue', id: 'use-deferred-value', Component: UseDeferredValueDemo, }, { title: 'Lazy Component Demo', id: 'lazy-component', Component: LazyComponentDemo, }, { title: 'Third Party React Component', id: 'third-party-component', Component: RechartExample, }, { title: 'Redux Demo', id: 'redux', Component: ReduxExample, }, { title: 'Mobx Demo', id: 'mobx', Component: MobxExample, }, { title: 'React Query and Zustand Demo', id: 'rq-zustand', Component: ReactQueryExample, }, ]; export default function App() { const { pathname } = useLocation(); const title = useMemo(() => { const pathWithoutHash = pathname.substr(1); const currentSelected = examples.find((obj) => obj.id === pathWithoutHash); return currentSelected ? currentSelected.title : examples[0].title; }, [pathname]); return (

Brahmos Demo{' '}

Brahmos is a Super charged UI library with exact same declarative APIs of React which uses native templates to separate static and dynamic parts of an application for faster updates.

{title}

{examples.map(({ id, Component }) => { return ( ); })}
); } ================================================ FILE: example/App.scss ================================================ @import url('https://fonts.googleapis.com/css?family=Nunito:400,700'); /** Colors **/ $brahmos-color-1: #efdc4f; $brahmos-color-2: #323330; $primary: $brahmos-color-2; // $primary-invert: #efdc4f; $primary-light: white; $text: $brahmos-color-2; $link: #2060c3; $menu-item-active-background-color: $brahmos-color-1; $menu-item-active-color: $brahmos-color-2; $family-sans-serif: 'Nunito', sans-serif; // $background: #02030e; @import '../node_modules/bulma/bulma.sass'; .app-container { min-height: 100vh; display: flex; flex-direction: column; .hero-body { display: flex; align-items: center; padding: 1.5rem; } .star-btn { vertical-align: middle; margin-left: 2rem; } .brahmos-logo, .github-logo { transition: all ease 400ms; } .brahmos-logo { width: 100px; height: 100px; top: 0; left: 0; position: absolute; } .github-logo { width: 60px; height: 60px; left: 50%; top: 150%; transform: translate(-50%, -50%); position: absolute; } .logo { margin-right: 1rem; min-width: 100px; height: 100px; border-radius: 50%; background: $brahmos-color-1; overflow: hidden; position: relative; cursor: pointer; &:hover { .brahmos-logo { top: -100%; } .github-logo { top: 50%; } } } .body { display: flex; flex: 1; } .menu-list a:not(.is-active) { border-bottom: 1px solid $border; } .small-width { width: 300px; } .control-wrap { display: flex; align-items: center; } .example-container { padding-bottom: 100px; position: relative; } .react-credit { background: white; position: fixed; padding: 1rem 0.75rem; border-top: 1px solid $border; bottom: 0; left: 25%; width: 75%; } } ================================================ FILE: example/Logos.js ================================================ import Brahmos from 'brahmos'; export function GithubLogo(props) { return ( ); } export function BrahmosLogo(props) { return ( ); } ================================================ FILE: example/common/ReactCredit.js ================================================ export default function ReactCredit({ name, link }) { return (

This demo is forked from {name} demo of React:
Source: {link}

); } ================================================ FILE: example/concurrent-mode/fakeApi.js ================================================ export function fetchProfileData(userId) { const userPromise = fetchUser(userId); const postsPromise = fetchPosts(userId); return { userId, user: wrapPromise(userPromise), posts: wrapPromise(postsPromise), }; } // Suspense integrations like Relay implement // a contract like this to integrate with React. // Real implementations can be significantly more complex. // Don't copy-paste this into your project! function wrapPromise(promise) { let status = 'pending'; let result; const suspender = promise.then( (r) => { status = 'success'; result = r; }, (e) => { status = 'error'; result = e; }, ); return { read() { if (status === 'pending') { throw suspender; } else if (status === 'error') { throw result; } else if (status === 'success') { return result; } }, }; } export function fetchUser(userId) { console.log('fetch user ' + userId + '...'); return new Promise((resolve) => { setTimeout(() => { console.log('fetched user ' + userId); switch (userId) { case 0: resolve({ name: 'Ringo Starr', }); break; case 1: resolve({ name: 'George Harrison', }); break; case 2: resolve({ name: 'John Lennon', }); break; case 3: resolve({ name: 'Paul McCartney', }); break; default: throw Error('Unknown user.'); } }, 1000 * Math.random()); }); } export function fetchPosts(userId) { console.log('fetch posts for ' + userId + '...'); return new Promise((resolve) => { setTimeout(() => { console.log('fetched posts for ' + userId); switch (userId) { case 0: resolve([ { id: 0, text: 'I get by with a little help from my friends', }, { id: 1, text: "I'd like to be under the sea in an octupus's garden", }, { id: 2, text: 'You got that sand all over your feet', }, ]); break; case 1: resolve([ { id: 0, text: 'Turn off your mind, relax, and float downstream', }, { id: 1, text: 'All things must pass', }, { id: 2, text: "I look at the world and I notice it's turning", }, ]); break; case 2: resolve([ { id: 0, text: 'Living is easy with eyes closed', }, { id: 1, text: "Nothing's gonna change my world", }, { id: 2, text: 'I am the walrus', }, ]); break; case 3: resolve([ { id: 0, text: 'Woke up, fell out of bed', }, { id: 1, text: 'Here, there, and everywhere', }, { id: 2, text: 'Two of us sending postcards, writing letters', }, ]); break; default: throw Error('Unknown user.'); } }, 2000 * Math.random()); }); } ================================================ FILE: example/concurrent-mode/index.js ================================================ /** * Forked from: https://codesandbox.io/s/jovial-lalande-26yep?file=/src/index.js:0-1646 */ import Brahmos, { useState, useTransition, Suspense } from 'brahmos'; import ReactCredit from '../common/ReactCredit'; import { fetchProfileData } from './fakeApi'; function getNextId(id) { return id === 3 ? 0 : id + 1; } const initialResource = fetchProfileData(0); function ProfileDetails({ resource }) { const user = resource.user.read(); return

{user.name}

; } function ProfileTimeline({ resource }) { const posts = resource.posts.read(); return ( ); } function ProfilePageWithoutTransition() { const [resource, setResource] = useState(initialResource); return (

Example of suspense for data fetching.

As soon as we click the button the view will go on receded state (shows loader), and as soon as partial data (skeleton or required data) is available it will starts showing partial data, and then show the complete data lazily.
Click Next to see the behaviour

Loading profile...}> Loading posts...}>
); } function ProfilePageWithTransition() { const [resource, setResource] = useState(initialResource); const [startTransition, isPending] = useTransition({ timeoutMs: 3000, }); return (

Example of transition.

Preferably, we should remain on the same screen with pending state until we have partial data (skeleton or required data).
Click Next to see the behaviour

{isPending ? ' Loading...' : null}
Loading profile...}> Loading posts...}>
); } function ProfilePageWithTransitionPreferred() { const [resource, setResource] = useState(initialResource); const [startTransition, isPending] = useTransition({ timeoutMs: 3000, }); return (

Another example of transition.

We can also wait in pending state until complete data is available for next page.
Click Next to see the behaviour

{isPending ? ' Loading...' : null}
Loading profile...}> Loading posts...}>
); } export default function App() { return ( <>

This demo demonstrates concurrent mode patterns in Brahmos.
It demonstrates Suspense for data fetch (Render as you fetch), Transitions and preferred rendering approach (Pending -> Skeleton -> Complete). Read more about it in official React Docs. https://reactjs.org/docs/concurrent-mode-patterns.html

On this demo APIs are mocked. Profile detail API responds between 0 - 1000ms and Post API responds between 0 - 2000ms

); } ================================================ FILE: example/context-api/index.js ================================================ import Brahmos, { Component, createContext, useContext, useState } from 'brahmos'; const BrahmosContext = createContext('Brahmos'); // context as static property class ContextStaticProperty extends Component { render() { const name = this.context; return
Hello {name}
; } } ContextStaticProperty.contextType = BrahmosContext; function ContextConsumer() { return {(name) =>
Hello {name}
}
; } function UseContext() { const name = useContext(BrahmosContext); return
Hello {name}
; } export default function ContextExample() { const [name, setName] = useState(); return (

This demo demonstrates usage of Context API in different way

setName(e.target.value)} />

ContextConsumer

Context Static Property

useContext Hook

<>

ContextConsumer without Provider

useContext without Provider

); } ================================================ FILE: example/error-boundaries/index.js ================================================ import Brahmos, { Component } from 'brahmos'; import ReactCredit from '../common/ReactCredit'; // Forked from: https://codepen.io/gaearon/pen/wqvxGa class ErrorBoundary extends Component { constructor(props) { super(props); this.state = { error: null, errorInfo: null }; } static getDerivedStateFromError(error) { return { error }; } // eslint-disable-next-line handle-callback-err componentDidCatch(error, errorInfo) { // Catch errors in any components below and re-render with error message this.setState({ errorInfo: errorInfo, }); // You can also log error messages to an error reporting service here } render() { if (this.state.error) { // Error path return (

Something went wrong.

{this.state.error && this.state.error.toString()}
{this.state.errorInfo && this.state.errorInfo.componentStack}
); } // Normally, just render children return this.props.children; } } class BuggyCounter extends Brahmos.Component { constructor(props) { super(props); this.state = { counter: 0 }; this.handleClick = this.handleClick.bind(this); } handleClick() { this.setState(({ counter }) => ({ counter: counter + 1, })); } render() { if (this.state.counter === 5) { // Simulate a JS error throw new Error('I crashed!'); } return (


); } } export default function ErrorBoundaryExample() { return (

Click on the numbers to increase the counters.
The counter is programmed to throw when it reaches 5. This simulates a JavaScript error in a component.


These two counters are inside the same error boundary. If one crashes, the error boundary will replace both of them.


These two counters are each inside of their own error boundary. So if one crashes, the other is not affected.

); } ================================================ FILE: example/food-app/food-app.css ================================================ .food-app { position: relative; display: flex; flex-direction: column; gap: 40px; text-align: center; padding-top: 20px; padding-bottom: 120px; width: 600px; min-height: 400px; background-color: #fffaf0; } .food-app > header { display: flex; align-items: center; justify-content: space-between; padding: 0 20px; } .food-app > footer { position: absolute; bottom: 0; padding: 30px 20px; width: 100%; } .veg-filter { margin-right: 5px; } .menu-list { display: flex; flex-direction: column; gap: 20px; } .menu-item { display: flex; gap: 10px; justify-content: space-between; align-items: center; padding: 0 20px; text-align: left; } .menu-item-title { display: flex; gap: 5px; } .menu-item-description { color: #555; font-size: 14px; margin-top: 5px; } .menu-btn-add { display: flex; align-items: center; gap: 5px; white-space: nowrap; background-color: transparent; border: 1px solid #cbd5e0; border-radius: 4px; padding: 5px 15px; font-size: 14px; } .menu-btn-group { display: flex; border: 1px solid #718096; border-radius: 4px; } .menu-btn-item { background-color: transparent; border: none; font-size: 14px; } .menu-item-qty { font-size: 14px; display: flex; align-items: center; padding: 5px 0; user-select: none; } .food-app-pay-btn { display: block; text-decoration: none; width: 100%; font-size: 20px; border: none; background-color: #ecc94b; color: #2d3748; padding: 20px 0; border-radius: 6px; font-weight: medium; } .icon-plus, .icon-minus { display: block; } ================================================ FILE: example/food-app/food-menu.json ================================================ [ { "id": "SN", "label": "Schezwan Noodles", "diet": "veg", "description": "Spicy Noodles soaked in Schezwan sauce and topped with exotic herbs", "price": 8 }, { "id": "SM", "label": "Sausage McMuffin", "description": "Lightly seasoned sausage patty and cheese slice on a hot toasted English muffin.", "price": 12 }, { "id": "MP", "label": "Mushroom Pizza", "diet": "veg", "description": "Loaded with 2 types of cheese and garlic herb sautéed mushrooms!! ", "price": 20 }, { "id": "CF", "label": "Chicken Foldover Spicy", "description": "Two succulent chicken patties, add some crisp lettuce.", "price": 10 }, { "id": "FF", "label": "French Fries", "diet": "veg", "description": "Crispy golden fries with a pinch of salt.", "price": 6 }, { "id": "BQ", "label": "BBQ Double Burger", "description": "Beef patties with a slice of cheese, shredded lettuce, tomato and onions", "price": 16 } ] ================================================ FILE: example/food-app/mobx/App.js ================================================ import { Fragment, useEffect, useState } from 'brahmos'; import { observer } from 'mobx-react-lite'; import { useRootStore } from './mobx'; import { MenuList, Message, PaymentFooter } from './Comps'; import { loadFoodData } from '../utils'; const App = observer(function App() { const rootStore = useRootStore(); const stateAPIStatus = useLoadFoodData(); function handleVegToggle() { rootStore.changeDiet(); } return (

Ordux

{stateAPIStatus === 'success' && (
)}
); }); function useLoadFoodData() { const [stateAPIStatus, setAPIStatus] = useState('idle'); const rootStore = useRootStore(); useEffect(() => { setAPIStatus('loading'); loadFoodData() .then((data) => { rootStore.loadMenu(data); setAPIStatus('success'); }) .catch((error) => { setAPIStatus('error'); }); }, [rootStore]); return stateAPIStatus; } export default App; ================================================ FILE: example/food-app/mobx/Comps.js ================================================ import { observer } from 'mobx-react-lite'; import { useRootStore } from './mobx'; export const MenuItem = observer(function MenuItem(props) { const { item } = props; const { quantity } = item; const rootStore = useRootStore(); function handleIncrement() { rootStore.addToCart(item); } function handleDecrement() { rootStore.removeFromCart(item); } const addBtn = ( ); const increaseBtn = ( ); const decreaseBtn = ( ); const qtyIndicator = (
{quantity}
); return (
  • {item.label}

    (${item.price})

    {item.description}

    {quantity === 0 ? ( addBtn ) : (
    {decreaseBtn} {qtyIndicator} {increaseBtn}
    )}
  • ); }); export const MenuList = observer(function MenuList() { const rootStore = useRootStore(); const { menuList } = rootStore; return (
      {menuList.map((item) => ( ))}
    ); }); export function Message(props) { const { status } = props; const messages = { loading: 'Loading...', error: ( <> Menu failed to load.
    Please try again... ), }; const messageText = messages[status]; if (!messageText) { return null; } return (
    {messageText}
    ); } export const PaymentFooter = observer(function PaymentFooter() { const rootStore = useRootStore(); const { cartPrice } = rootStore; return ( ); }); // source- https://feathericons.com/ export function IconPlus() { return ( ); } // source- https://feathericons.com/ export function IconMinus() { return ( ); } ================================================ FILE: example/food-app/mobx/index.js ================================================ import { StrictMode } from 'brahmos'; import App from './App'; import { RootStore, RootStoreProvider } from './mobx'; import '../food-app.css'; export default function ReduxExample() { return (

    This demo demonstrates Brahmos compatibility with Mobx.
    The example is forked from{' '} https://github.com/itaditya/redux-hooks-for-food-delivery/tree/aditya-redux-to-mobx

    ); } ================================================ FILE: example/food-app/mobx/mobx.js ================================================ import { createContext, useContext, useEffect } from 'brahmos'; import { makeAutoObservable, toJS } from 'mobx'; export class RootStore { diet = 'all'; menu = []; constructor() { makeAutoObservable(this); } changeDiet() { const newDiet = this.diet === 'veg' ? 'all' : 'veg'; this.diet = newDiet; } loadMenu(menuList) { const menu = menuList.map((item) => { return { ...item, quantity: 0, }; }); this.menu = menu; } addToCart(item) { item.quantity += 1; } removeFromCart(item) { if (item.quantity !== 0) { item.quantity -= 1; } } get menuList() { if (this.diet === 'all') { return this.menu; } return this.menu.filter((item) => item.diet === this.diet); } get cartPrice() { let total = 0; this.menuList.forEach((item) => { total += item.price * item.quantity; }); return total; } } const RootStoreContext = createContext(); export const RootStoreProvider = RootStoreContext.Provider; export const RootStoreConsumer = RootStoreContext.Consumer; export function useRootStore() { const rootStore = useContext(RootStoreContext); if (rootStore === undefined) { throw new Error('useRootStore must be used within a RootStoreProvider'); } useEffect(() => { if (process.env.NODE_ENV === 'development' && !window.mobx) { window.mobx = { rootStore, toJS, }; } }, [rootStore]); return rootStore; } ================================================ FILE: example/food-app/react-query/App.js ================================================ import { Fragment } from 'brahmos'; import { useDietStore } from './zustand'; import { MenuList, Message, PaymentFooter } from './Comps'; import { useLoadFoodQuery, useMenuList } from './hooks'; export default function App() { const { diet, changeDiet } = useDietStore(); const menuQuery = useLoadFoodQuery(); const menuList = useMenuList(); const stateAPIStatus = menuQuery.status; function handleVegToggle() { changeDiet(); } return (

    Ordux

    {stateAPIStatus === 'success' && (
    )}
    ); } ================================================ FILE: example/food-app/react-query/Comps.js ================================================ import Brahmos from 'brahmos'; import { useCartStore } from './zustand'; import { useCartPrice } from './hooks'; export function MenuItem(props) { const { item } = props; const { cartByIds, addToCart, removeFromCart } = useCartStore(); const quantity = cartByIds[item.id]?.quantity ?? 0; function handleIncrement() { addToCart(item.id); } function handleDecrement() { removeFromCart(item.id); } const addBtn = ( ); const increaseBtn = ( ); const decreaseBtn = ( ); const qtyIndicator = (
    {quantity}
    ); return (
  • {item.label}

    (${item.price})

    {item.description}

    {quantity === 0 ? ( addBtn ) : (
    {decreaseBtn} {qtyIndicator} {increaseBtn}
    )}
  • ); } function PureMenuList(props) { console.log('MenuList Re-Render'); const { menuList } = props; return (
      {menuList.map((item) => ( ))}
    ); } export const MenuList = Brahmos.memo(PureMenuList); export function Message(props) { const { status } = props; const messages = { loading: 'Loading...', error: ( <> Menu failed to load.
    Please try again... ), }; const messageText = messages[status]; if (!messageText) { return null; } return (
    {messageText}
    ); } export function PaymentFooter() { const cartPrice = useCartPrice(); return ( ); } // source- https://feathericons.com/ export function IconPlus() { return ( ); } // source- https://feathericons.com/ export function IconMinus() { return ( ); } ================================================ FILE: example/food-app/react-query/hooks.js ================================================ import { useMemo } from 'brahmos'; import { useQuery } from 'react-query'; import { useDietStore, useCartStore } from './zustand'; import { loadFoodData } from '../utils'; export function useLoadFoodQuery() { const menuQuery = useQuery('menu', loadFoodData); return menuQuery; } export function useMenuList() { const diet = useDietStore((state) => state.diet); const menuQuery = useLoadFoodQuery(); const data = menuQuery.data || []; const menuList = useMemo(() => { const computeMenuList = data.filter((item) => { if (diet === 'all') { return item; } return item.diet === diet; }); return computeMenuList; }, [diet, data]); return menuList; } export function useCartPrice() { const cartByIds = useCartStore((state) => state.cartByIds); const menuList = useMenuList(); const cartPrice = useMemo(() => { let cartPrice = 0; menuList.forEach((item) => { const cartItem = cartByIds[item.id] || { quantity: 0 }; cartPrice += item.price * cartItem.quantity; }); return cartPrice; }, [cartByIds, menuList]); return cartPrice; } ================================================ FILE: example/food-app/react-query/index.js ================================================ import { StrictMode } from 'brahmos'; import { QueryCache, ReactQueryCacheProvider } from 'react-query'; import App from './App'; import '../food-app.css'; const queryCache = new QueryCache({ defaultConfig: { queries: { staleTime: 300000, // 5 minutes }, }, }); export default function ReduxExample() { return (

    This demo demonstrates Brahmos compatibility with React Query and Zustand.
    The example is forked from{' '} https://github.com/itaditya/redux-hooks-for-food-delivery/tree/aditya-redux-to-rq-zustand

    ); } ================================================ FILE: example/food-app/react-query/zustand.js ================================================ import create from 'zustand'; export const useDietStore = create((set) => ({ diet: 'all', changeDiet: () => set((state) => ({ diet: state.diet === 'veg' ? 'all' : 'veg' })), })); export const useCartStore = create((set, get) => ({ cartByIds: {}, addToCart(itemId) { const { cartByIds } = get(); const cartItem = cartByIds[itemId] || { quantity: 0, }; cartItem.quantity += 1; const newCart = { ...cartByIds, [itemId]: cartItem, }; set({ cartByIds: newCart, }); }, removeFromCart(itemId) { const { cartByIds } = get(); const cartItem = cartByIds[itemId]; if (!cartItem) { return; } cartItem.quantity -= 1; const newCart = { ...cartByIds, [itemId]: cartItem, }; set({ cartByIds: newCart, }); }, })); ================================================ FILE: example/food-app/redux/App.js ================================================ import { Fragment, useEffect, useState } from 'brahmos'; import { shallowEqual, useSelector, useDispatch } from 'react-redux'; import { ACTIONS } from './redux'; import { MenuList, Message, PaymentFooter } from './Comps'; import { loadFoodData } from '../utils'; export default function App() { const diet = useSelector((state) => state.diet); const dispatch = useDispatch(); const stateAPIStatus = useLoadFoodData(); const menuList = useSelector(selectorMenu, shallowEqual); useEffect(() => { console.log('SERVER_EVENT: menu list changed'); }, [menuList]); function handleVegToggle() { dispatch({ type: ACTIONS.CHANGE_DIET, }); } return (

    Ordux

    {stateAPIStatus === 'success' && (
    )}
    ); } function useLoadFoodData() { const [stateAPIStatus, setAPIStatus] = useState('idle'); const dispatch = useDispatch(); useEffect(() => { setAPIStatus('loading'); loadFoodData() .then((data) => { dispatch({ type: ACTIONS.LOAD_MENU, payload: { menu: data, }, }); setAPIStatus('success'); }) .catch((error) => { setAPIStatus('error'); }); }, [dispatch]); return stateAPIStatus; } function selectorMenu(state) { const { diet, menuIdList, menuById } = state; const menuId = menuIdList[diet]; const menuList = []; menuId.forEach((id) => { menuList.push(menuById[id]); }); return menuList; } ================================================ FILE: example/food-app/redux/Comps.js ================================================ import Brahmos from 'brahmos'; import { useSelector, useDispatch } from 'react-redux'; import { ACTIONS } from './redux'; export function MenuItem(props) { const { item } = props; const cartByIds = useSelector((state) => state.cartByIds); const dispatch = useDispatch(); const quantity = cartByIds[item.id]?.quantity ?? 0; function handleIncrement() { dispatch({ type: ACTIONS.ADD_TO_CART, payload: { itemId: item.id, }, }); } function handleDecrement() { dispatch({ type: ACTIONS.REMOVE_FROM_CART, payload: { itemId: item.id, }, }); } const addBtn = ( ); const increaseBtn = ( ); const decreaseBtn = ( ); const qtyIndicator = (
    {quantity}
    ); return (
  • {item.label}

    (${item.price})

    {item.description}

    {quantity === 0 ? ( addBtn ) : (
    {decreaseBtn} {qtyIndicator} {increaseBtn}
    )}
  • ); } function PureMenuList(props) { console.log('MenuList Re-Render'); const { menuList } = props; return (
      {menuList.map((item) => ( ))}
    ); } export const MenuList = Brahmos.memo(PureMenuList); export function Message(props) { const { status } = props; const messages = { loading: 'Loading...', error: ( <> Menu failed to load.
    Please try again... ), }; const messageText = messages[status]; if (!messageText) { return null; } return (
    {messageText}
    ); } function selectorCartPrice(state) { const { cartByIds, menuById } = state; let cartPrice = 0; const cartKeys = Object.keys(cartByIds); cartKeys.forEach((id) => { const item = menuById[id]; const cartItem = cartByIds[id]; const price = cartItem.quantity * item.price; cartPrice += price; }); return cartPrice; } export function PaymentFooter() { const cartPrice = useSelector(selectorCartPrice); return ( ); } // source- https://feathericons.com/ export function IconPlus() { return ( ); } // source- https://feathericons.com/ export function IconMinus() { return ( ); } ================================================ FILE: example/food-app/redux/index.js ================================================ import { StrictMode } from 'brahmos'; import { Provider } from 'react-redux'; import App from './App'; import { createReduxStore } from './redux'; import '../food-app.css'; export default function ReduxExample() { return (

    This demo demonstrates Brahmos compatibility with Redux.
    The example is forked from{' '} https://github.com/itaditya/redux-hooks-for-food-delivery/

    ); } ================================================ FILE: example/food-app/redux/redux.js ================================================ import { createStore } from 'redux'; export const ACTIONS = { CHANGE_DIET: 'CHANGE_DIET', LOAD_MENU: 'LOAD_MENU', ADD_TO_CART: 'ADD_TO_CART', REMOVE_FROM_CART: 'REMOVE_FROM_CART', }; const initialState = { diet: 'all', menuById: {}, menuIdList: { all: [], veg: [], }, cartByIds: {}, }; function foodReducer(state = initialState, action) { switch (action.type) { case ACTIONS.CHANGE_DIET: { const { diet } = state; const newDiet = diet === 'veg' ? 'all' : 'veg'; return { ...state, diet: newDiet, cartByIds: {}, }; } case ACTIONS.LOAD_MENU: { const { menu } = action.payload; const menuById = {}; menu.forEach((item) => { menuById[item.id] = item; }); const allMenuId = menu.map((item) => item.id); const vegMenuId = menu.filter((item) => item.diet === 'veg').map((item) => item.id); return { ...state, menuById, menuIdList: { all: allMenuId, veg: vegMenuId, }, }; } case ACTIONS.ADD_TO_CART: { const { itemId } = action.payload; const { cartByIds } = state; const cartItem = cartByIds[itemId] || { quantity: 0, }; cartItem.quantity += 1; const newCart = { ...cartByIds, [itemId]: cartItem, }; return { ...state, cartByIds: newCart, }; } case ACTIONS.REMOVE_FROM_CART: { const { itemId } = action.payload; const { cartByIds } = state; const cartItem = cartByIds[itemId]; if (!cartItem) { return state; } cartItem.quantity -= 1; const newCart = { ...cartByIds, [itemId]: cartItem, }; return { ...state, cartByIds: newCart, }; } default: return state; } } const enableReduxDevTools = window.__REDUX_DEVTOOLS_EXTENSION__?.(); export function createReduxStore() { const store = createStore(foodReducer, enableReduxDevTools); return store; } ================================================ FILE: example/food-app/utils.js ================================================ import foodMenu from './food-menu.json'; export async function loadFoodData() { return foodMenu; } ================================================ FILE: example/hooks/index.js ================================================ import Brahmos, { useState, useEffect, useRef } from 'brahmos'; import ReactCredit from '../common/ReactCredit'; function useInterval(callback, delay) { const savedCallback = useRef(); // Remember the latest function. useEffect(() => { savedCallback.current = callback; }, [callback]); // Set up the interval. useEffect(() => { function tick() { savedCallback.current(); } if (delay !== null) { const id = setInterval(tick, delay); return () => clearInterval(id); } }, [delay]); } export default function Counter() { const [count, setCount] = useState(0); const [delay, setDelay] = useState(1000); const [isRunning, setIsRunning] = useState(true); useInterval( () => { // Your custom logic here setCount(count + 1); }, isRunning ? delay : null, ); function handleDelayChange(e) { setDelay(Number(e.target.value)); } function handleIsRunningChange(e) { setIsRunning(e.target.checked); } return ( <>

    This demo demonstrates usage of hooks in Brahmos. The example here uses useState, useRef and useEffect hooks to make setInterval declarative.

    {count}

    Running
    ); } ================================================ FILE: example/index.html ================================================ Brahmos Demo
    ================================================ FILE: example/index.js ================================================ import { render } from 'brahmos'; import { HashRouter as Router } from 'react-router-dom'; import App from './App.js'; function RootApp() { return ( ); } render(, document.getElementById('app')); ================================================ FILE: example/lazy-component/index.js ================================================ import Brahmos, { Suspense, lazy } from 'brahmos'; const LazyTodoList = lazy(() => { return new Promise((resolve) => { setTimeout(() => { resolve(import('../todo-list/index')); }, 1500); }); }); export default function LazyExample() { return ( Loading TodoList !!!}>

    This Todo list is loaded lazily.

    ); } ================================================ FILE: example/portals/index.js ================================================ import Brahmos, { createPortal, useState } from 'brahmos'; function Modal({ onClose }) { return (

    Modal title

    Hello World

    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla accumsan, metus ultrices eleifend gravida, nulla nunc varius lectus, nec rutrum justo nibh eu lectus. Ut vulputate semper dui. Fusce erat odio, sollicitudin vel erat vel, interdum mattis neque.

    ); } export default function CreatePortalExample() { const [display, setDisplay] = useState(false); return (
    {display && createPortal( setDisplay(false)} />, document.querySelector('#portal-container'), )}

    Click button to open modal. The modal will open in another root element.

    ); } ================================================ FILE: example/sierpinski-triangle/index.js ================================================ import { Component, unstable_deferredUpdates, unstable_syncUpdates } from 'brahmos'; /** * Source: https://github.com/facebook/react/blob/master/fixtures/fiber-triangle/index.html */ const dotStyle = { position: 'absolute', background: '#61dafb', font: 'normal 15px sans-serif', textAlign: 'center', cursor: 'pointer', }; const containerStyle = { position: 'absolute', transformOrigin: '0 0', left: '50%', top: '50%', width: '10px', height: '10px', background: '#eee', }; var targetSize = 25; class Dot extends Component { constructor() { super(); this.state = { hover: false }; } enter() { this.setState({ hover: true, }); } leave() { this.setState({ hover: false, }); } render() { var props = this.props; var s = props.size * 1.3; var style = { ...dotStyle, width: s + 'px', height: s + 'px', left: props.x + 'px', top: props.y + 'px', borderRadius: s / 2 + 'px', lineHeight: s + 'px', background: this.state.hover ? '#ff0' : dotStyle.background, }; return (
    this.enter()} onMouseLeave={() => this.leave()}> {this.state.hover ? '*' + props.text + '*' : props.text}
    ); } } class SierpinskiTriangle extends Component { shouldComponentUpdate(nextProps) { var o = this.props; var n = nextProps; return !(o.x === n.x && o.y === n.y && o.s === n.s && o.children === n.children); } render() { let { x, y, s, children } = this.props; if (s <= targetSize) { return ( ); return r; } var newSize = s / 2; var slowDown = true; if (slowDown) { var e = performance.now() + 0.8; while (performance.now() < e) { // Artificially long execution time. } } s /= 2; return [ {children} , {children} , {children} , ]; } } class Toggle extends Component { constructor(props) { super(); this.onChange = this.onChange.bind(this); } onChange(event) { this.props.onChange(event.target.value === 'on'); } render() { const value = this.props.value; return ( ); } } class SierpinskiWrapper extends Component { constructor() { super(); this.state = { seconds: 0, useTimeSlicing: true, }; this.tick = this.tick.bind(this); this.onTimeSlicingChange = this.onTimeSlicingChange.bind(this); } componentDidMount() { this.intervalID = setInterval(this.tick, 1000); } tick() { if (this.state.useTimeSlicing) { // Update is time-sliced. unstable_deferredUpdates(() => { this.setState({ seconds: (this.state.seconds % 10) + 1 }); }); } else { // Update is not time-sliced. Causes demo to stutter. this.setState({ seconds: (this.state.seconds % 10) + 1 }); } } onTimeSlicingChange(value) { this.setState(() => ({ useTimeSlicing: value })); } componentWillUnmount() { clearInterval(this.intervalID); } render() { const seconds = this.state.seconds; const elapsed = this.props.elapsed; const t = (elapsed / 1000) % 10; const scale = 1 + (t > 5 ? 10 - t : t) / 10; const transform = 'scaleX(' + scale / 2.1 + ') scaleY(0.7) translateZ(0.1px)'; return (

    Time-slicing

    Toggle this and observe the effect

    {seconds}
    ); } } export default class DemoApp extends Component { constructor() { super(); this.start = Date.now(); this.state = { elapsed: this.start, }; } componentDidMount() { this.updateElapsed(); } componentWillUnmount() { cancelAnimationFrame(this.animationFrameId); } updateElapsed() { this.animationFrameId = requestAnimationFrame(() => { unstable_syncUpdates(() => { this.setState({ elapsed: Date.now() - this.start, }); }); this.updateElapsed(); }); } render() { const { elapsed } = this.state; return ; } } ================================================ FILE: example/suspense-list/fakeApi.js ================================================ export function fetchProfileData() { const userPromise = fetchUser(); const postsPromise = fetchPosts(); const triviaPromise = fetchTrivia(); return { user: wrapPromise(userPromise), posts: wrapPromise(postsPromise), trivia: wrapPromise(triviaPromise), }; } // Suspense integrations like Relay implement // a contract like this to integrate with React. // Real implementations can be significantly more complex. // Don't copy-paste this into your project! function wrapPromise(promise) { let status = 'pending'; let result; const suspender = promise.then( (r) => { status = 'success'; result = r; }, (e) => { status = 'error'; result = e; }, ); return { read() { if (status === 'pending') { throw suspender; } else if (status === 'error') { throw result; } else if (status === 'success') { return result; } }, }; } function fetchUser() { console.log('fetch user...'); return new Promise((resolve) => { setTimeout(() => { console.log('fetched user'); resolve({ name: 'Ringo Starr', }); }, 500); }); } const ringoPosts = [ { id: 0, text: 'I get by with a little help from my friends', }, { id: 1, text: "I'd like to be under the sea in an octupus's garden", }, { id: 2, text: 'You got that sand all over your feet', }, ]; function fetchPosts() { const ringoPostsAtTheTime = ringoPosts; console.log('fetch posts...'); return new Promise((resolve) => { setTimeout(() => { console.log('fetched posts'); resolve(ringoPostsAtTheTime); }, 3000 * Math.random()); }); } function fetchTrivia() { return new Promise((resolve) => { setTimeout(() => { resolve([ { id: 1, text: 'The nickname "Ringo" came from his habit of wearing numerous rings.', }, { id: 2, text: 'Plays the drums left-handed with a right-handed drum set.', }, { id: 3, text: 'Nominated for one Daytime Emmy Award, but did not win', }, ]); }, 3000 * Math.random()); }); } ================================================ FILE: example/suspense-list/index.js ================================================ /** * Forked from: https://codesandbox.io/s/black-wind-byilt */ import Brahmos, { SuspenseList, Suspense, useEffect, useState } from 'brahmos'; import ReactCredit from '../common/ReactCredit'; import { fetchProfileData } from './fakeApi'; function ProfileDetails({ resource }) { const user = resource.user.read(); return

    {user.name}

    ; } function ProfileTimeline({ resource }) { const posts = resource.posts.read(); return (
      {posts.map((post) => (
    • {post.text}
    • ))}
    ); } function ProfileTrivia({ resource }) { const trivia = resource.trivia.read(); return ( <>

    Fun Facts

      {trivia.map((fact) => (
    • {fact.text}
    • ))}
    ); } function ProfilePage({ revealOrder = 'forwards', tail }) { const [resource, setResource] = useState(); return (
    Loading...}> {resource && ( <> Loading posts...}> Loading fun facts...}> )}
    ); } function App() { return ( <>

    This demo demonstrates Suspense List Implementation on Brahmos.
    The APIs are mocked to respond in between 0-3000ms.


    Suspense list with forwards reveal order.

    Though api response time can defer, in forwards reveal order, it guarantees the second suspense will not reveal before the first suspense. In this example fun facts will never be revealed before the post.
    Click on button multiple times to see the effect.


    Suspense list with backwards reveal order.

    In backwards reveal order, it guarantees the suspense are revealed from last to first. In this example post will never be revealed before the fun fact.
    Click on button multiple times to see the effect.


    Suspense list with together reveal order.

    In together reveal order, all the suspense are revealed together.


    Suspense list with collapsed loading state.

    Only one loading will be shown inside the SuspenseList at a time.


    Suspense list with no loading state.

    No loading will be shown inside the SuspenseList when tail prop is set to hidden.

    ); } export default App; ================================================ FILE: example/svg-chart/index.js ================================================ import Brahmos, { Component } from 'brahmos'; function Rect({ height, index }) { return ( ); } function Chart({ data }) { return ( {data.map((height, index) => ( ))} ); } class SVGExample extends Component { state = { data: [99, 44, 11, 55, 33, 115, 4], }; _shuffuleArray(array) { var j, temp, i; for (i = array.length; i; i--) { j = Math.floor(Math.random() * i); temp = array[i - 1]; array[i - 1] = array[j]; array[j] = temp; } return array; } shuffule = () => this.setState({ data: this._shuffuleArray(this.state.data) }); render() { const { data } = this.state; return (

    This demo demonstrate usage of dynamic svg in Brahmos.

    ); } } export default SVGExample; ================================================ FILE: example/third-party-component/index.js ================================================ import { PureComponent } from 'brahmos'; import { BarChart, Bar, Cell, XAxis, YAxis, CartesianGrid, Tooltip, Legend } from 'recharts'; const data = [ { name: 'Page A', uv: 4000, pv: 2400, amt: 2400, }, { name: 'Page B', uv: 3000, pv: 1398, amt: 2210, }, { name: 'Page C', uv: 2000, pv: 9800, amt: 2290, }, { name: 'Page D', uv: 2780, pv: 3908, amt: 2000, }, { name: 'Page E', uv: 1890, pv: 4800, amt: 2181, }, { name: 'Page F', uv: 2390, pv: 3800, amt: 2500, }, { name: 'Page G', uv: 3490, pv: 4300, amt: 2100, }, ]; export default class RechartExample extends PureComponent { render() { return (

    This demo demonstrate usage of third party react component in Brahmos. In this example we are using{' '} recharts for rendering chart which internally uses{' '} react-smooth . Both of them are react based libraries.

    ); } } ================================================ FILE: example/time-slicing/Chart.js ================================================ import Brahmos, { PureComponent } from 'brahmos'; import { VictoryArea, VictoryAxis, VictoryChart, VictoryBar, VictoryTheme, VictoryScatter, VictoryStack, } from 'victory'; const colors = ['#fff489', '#fa57c1', '#b166cc', '#7572ff', '#69a6f9']; export default class Charts extends PureComponent { render() { const streamData = this.props.data; return (
    colors[d.x % 5], }, }} /> colors[d.x % 5], stroke: 'none', padding: 5, }, }} />
    {streamData.map((data, i) => ( ))}
    ); } } ================================================ FILE: example/time-slicing/Clock.js ================================================ import Brahmos, { createRef, PureComponent } from 'brahmos'; const SPEED = 0.003 / Math.PI; const FRAMES = 10; export default class Clock extends PureComponent { faceRef = createRef(); arcGroupRef = createRef(); clockHandRef = createRef(); frame = null; hitCounter = 0; rotation = 0; t0 = Date.now(); arcs = []; animate = () => { const now = Date.now(); const td = now - this.t0; this.rotation = (this.rotation + SPEED * td) % (2 * Math.PI); this.t0 = now; this.arcs.push({ rotation: this.rotation, td }); let lx, ly, tx, ty; if (this.arcs.length > FRAMES) { this.arcs.forEach(({ rotation, td }, i) => { lx = tx; ly = ty; const r = 145; tx = 155 + r * Math.cos(rotation); ty = 155 + r * Math.sin(rotation); const bigArc = SPEED * td < Math.PI ? '0' : '1'; const path = `M${tx} ${ty}A${r} ${r} 0 ${bigArc} 0 ${lx} ${ly}L155 155`; const hue = 120 - Math.min(120, td / 4); const colour = `hsl(${hue}, 100%, ${60 - i * (30 / FRAMES)}%)`; if (i !== 0) { const arcEl = this.arcGroupRef.current.children[i - 1]; arcEl.setAttribute('d', path); arcEl.setAttribute('fill', colour); } }); this.clockHandRef.current.setAttribute('d', `M155 155L${tx} ${ty}`); this.arcs.shift(); } if (this.hitCounter > 0) { this.faceRef.current.setAttribute('fill', `hsla(0, 0%, ${this.hitCounter}%, 0.95)`); this.hitCounter -= 1; } else { this.hitCounter = 0; this.faceRef.current.setAttribute('fill', 'hsla(0, 0%, 5%, 0.95)'); } this.frame = requestAnimationFrame(this.animate); }; componentDidMount() { this.frame = requestAnimationFrame(this.animate); if (this.faceRef.current) { this.faceRef.current.addEventListener('click', this.handleClick); } } componentDidUpdate() { console.log('componentDidUpdate()', this.faceRef.current); } componentWillUnmount() { this.faceRef.current.removeEventListener('click', this.handleClick); if (this.frame) { cancelAnimationFrame(this.frame); } } handleClick = (e) => { e.stopPropagation(); this.hitCounter = 50; }; render() { const paths = new Array(FRAMES); for (let i = 0; i < FRAMES; i++) { paths.push(); } return (
    {paths}
    ); } } ================================================ FILE: example/time-slicing/index.js ================================================ import Brahmos, { PureComponent } from 'brahmos'; import { flushSync } from 'react-dom'; import Scheduler from 'scheduler'; import _ from 'lodash'; import Charts from './Charts'; import Clock from './Clock'; // import './index.css'; const cachedData = new Map(); class App extends PureComponent { state = { value: '', strategy: 'sync', showDemo: true, showClock: false, }; // Random data for the chart getStreamData(input) { if (cachedData.has(input)) { return cachedData.get(input); } const multiplier = input.length !== 0 ? input.length : 1; const complexity = (parseInt(window.location.search.substring(1), 10) / 100) * 25 || 25; const data = _.range(5).map((t) => _.range(complexity * multiplier).map((j, i) => { return { x: j, y: (t + 1) * _.random(0, 255), }; }), ); cachedData.set(input, data); return data; } componentDidMount() { window.addEventListener('keydown', (e) => { if (e.key.toLowerCase() === '?') { e.preventDefault(); this.setState((state) => ({ showClock: !state.showClock, })); } }); } handleChartClick = (e) => { if (this.state.showDemo) { if (e.shiftKey) { this.setState({ showDemo: false }); } return; } if (this.state.strategy !== 'async') { flushSync(() => { this.setState((state) => ({ showDemo: !state.showDemo, })); }); return; } if (this._ignoreClick) { return; } this._ignoreClick = true; Scheduler.unstable_next(() => { this.setState({ showDemo: true }, () => { this._ignoreClick = false; }); }); }; debouncedHandleChange = _.debounce((value) => { if (this.state.strategy === 'debounced') { flushSync(() => { this.setState({ value: value }); }); } }, 1000); renderOption(strategy, label) { const { strategy: currentStrategy } = this.state; return ( ); } handleChange = (e) => { const value = e.target.value; const { strategy } = this.state; switch (strategy) { case 'sync': this.setState({ value }); break; case 'debounced': this.debouncedHandleChange(value); break; case 'async': // TODO: useTransition hook instead. setTimeout(() => { this.setState({ value }); }, 0); break; default: break; } }; render() { const { showClock } = this.state; const data = this.getStreamData(this.state.value); return (
    {this.renderOption('sync', 'Synchronous')} {this.renderOption('debounced', 'Debounced')} {this.renderOption('async', 'Concurrent')}
    {this.state.showDemo && }
    ); } } ================================================ FILE: example/todo-list/index.js ================================================ import Brahmos, { Component } from 'brahmos'; // Source: https://stackoverflow.com/a/12646864 function shuffleArray(array) { for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; } return array; } // Source: https://stackoverflow.com/a/25821830 function randomColor() { return `#${Math.floor(Math.random() * 16777215) .toString(16) .padStart(6, '0')}`; } export default class TodoList extends Component { static defaultProps = { maxCount: 5, showDescription: true, }; state = { todos: [], text: '' }; componentDidUpdate(prevProps, prevState) { /** * Imperatively updating background on first color-code item, * to demonstrate element are persisted on shuffle */ if (prevState.todos.length !== this.state.todos.length) { document.querySelector('.color-code').style.background = randomColor(); } } setText = (e) => { this.setState({ text: e.target.value }); }; addTodo = () => { const { todos, text } = this.state; const { maxCount } = this.props; if (!text || maxCount === todos.length) { return; } const todo = { text, id: todos.length, }; this.setState({ todos: [todo, ...todos], text: '' }); }; shuffle = () => { const { todos } = this.state; this.setState({ todos: shuffleArray(todos) }); }; render() { const { todos, text } = this.state; const { maxCount, showDescription } = this.props; return ( <> {showDescription && (

    This demo demonstrates states, list and keyed list in Brahmos. A color code is also added imperatively through DOM APIs to demonstrate keyed list are persisted (just rearranged) on addition or shuffle.

    )}

    Latest todo item is added on top. Add 3 items or more to shuffle the list

      {todos.map((todo) => (
    • {' '} {todo.text}
    • ))}
    {!!todos.length && maxCount > todos.length && ( You can add {maxCount - todos.length} items to this list   )} {maxCount === todos.length && List is Full  } {todos.length > 2 && ( )}
    ); } } ================================================ FILE: example/use-deferred-value/MySlowList.js ================================================ import Brahmos, { PureComponent } from 'brahmos'; // Note: this file is exactly the same in both examples. function ListItem({ children }) { const now = performance.now(); while (performance.now() - now < 1.5) { // Note: this is an INTENTIONALLY EMPTY loop that // DOES NOTHING for 1.5 milliseconds for EACH ITEM. // // It's meant to emulate what happens in a deep // component tree with calculations and other // work performed inside components that can't // trivially be optimized or removed. } return
    {children}
    ; } export default class MySlowList extends PureComponent { render() { const { text } = this.props; const items = []; const ln = text.length * 5; for (let i = 0; i < ln; i++) { items.push( Result #{i} for "{text}" , ); } return ( <>

    Results for "{text}":

      {items}
    ); } } ================================================ FILE: example/use-deferred-value/index.js ================================================ import Brahmos, { useDeferredValue, useState, useEffect } from 'brahmos'; import MySlowList from './MySlowList'; import ReactCredit from '../common/ReactCredit'; /** * Forked from: * https://codesandbox.io/s/infallible-dewdney-9fkv9 */ function Toggle({ concurrentMode, setConcurrentMode }) { return (
    Concurrent Mode:  
    ); } export default function App() { const [text, setText] = useState(''); const [concurrentMode, setConcurrentMode] = useState(true); // This is a new feature in Concurrent Mode. // This value is allowed to "lag behind" the text input: const deferredText = useDeferredValue(text, { timeoutMs: 5000, }); function handleChange(e) { setText(e.target.value); } return (

    Toggle concurrentMode and see the effect in input


    Here we create 5 list item for each character in text box. Plus each of the list item completely blocks the main thread for 1.5 milliseconds.

    On concurrent mode we can see the app is able to stay responsive even the number of list increases and only delays displaying the list. While in normal mode the input starts getting janky as soon as the size of list grows.


    {/* Pass the "lagging" value to the list */}
    ); } ================================================ FILE: example/use-deferred-value-suspense/fakeApi.js ================================================ export function fetchProfileData(userId) { const userPromise = fetchUser(userId); const postsPromise = fetchPosts(userId); return { userId, user: wrapPromise(userPromise), posts: wrapPromise(postsPromise), }; } // Suspense integrations like Relay implement // a contract like this to integrate with React. // Real implementations can be significantly more complex. // Don't copy-paste this into your project! function wrapPromise(promise) { let status = 'pending'; let result; const suspender = promise.then( (r) => { status = 'success'; result = r; }, (e) => { status = 'error'; result = e; }, ); return { read() { if (status === 'pending') { throw suspender; } else if (status === 'error') { throw result; } else if (status === 'success') { return result; } }, }; } export function fetchUser(userId) { console.log('fetch user ' + userId + '...'); return new Promise((resolve) => { setTimeout(() => { console.log('fetched user ' + userId); switch (userId) { case 0: resolve({ name: 'Ringo Starr', }); break; case 1: resolve({ name: 'George Harrison', }); break; case 2: resolve({ name: 'John Lennon', }); break; case 3: resolve({ name: 'Paul McCartney', }); break; default: throw Error('Unknown user.'); } }, 300); }); } export function fetchPosts(userId) { console.log('fetch posts for ' + userId + '...'); return new Promise((resolve) => { setTimeout(() => { console.log('fetched posts for ' + userId); switch (userId) { case 0: resolve([ { id: 0, text: 'I get by with a little help from my friends', }, { id: 1, text: "I'd like to be under the sea in an octupus's garden", }, { id: 2, text: 'You got that sand all over your feet', }, ]); break; case 1: resolve([ { id: 0, text: 'Turn off your mind, relax, and float downstream', }, { id: 1, text: 'All things must pass', }, { id: 2, text: "I look at the world and I notice it's turning", }, ]); break; case 2: resolve([ { id: 0, text: 'Living is easy with eyes closed', }, { id: 1, text: "Nothing's gonna change my world", }, { id: 2, text: 'I am the walrus', }, ]); break; case 3: resolve([ { id: 0, text: 'Woke up, fell out of bed', }, { id: 1, text: 'Here, there, and everywhere', }, { id: 2, text: 'Two of us sending postcards, writing letters', }, ]); break; default: throw Error('Unknown user.'); } }, 1500); }); } ================================================ FILE: example/use-deferred-value-suspense/index.js ================================================ /** * Forked from: https://codesandbox.io/s/vigorous-keller-3ed2b */ import Brahmos, { useDeferredValue, useState, useTransition, Suspense } from 'brahmos'; import ReactCredit from '../common/ReactCredit'; import { fetchProfileData } from './fakeApi'; function getNextId(id) { return id === 3 ? 0 : id + 1; } const initialResource = fetchProfileData(0); function App() { const [resource, setResource] = useState(initialResource); const [startTransition, isPending] = useTransition({ timeoutMs: 3000, }); return ( <>

    This demo demonstrates how we can use useDeferredValue to prevent loading state on non important data.
    Read more about it in official React Docs. https://reactjs.org/docs/concurrent-mode-patterns.html#deferring-a-value

    Here the profile detail API is mocked to respond in 300ms and post API is mocked to respond in 1500ms. The app will keep showing stale content for post data until its loaded or for max 5 seconds if you keep pressing next before a post gets a chance to load.

    {isPending ? ' Loading...' : null}
    ); } function ProfilePage({ resource }) { const deferredResource = useDeferredValue(resource, { timeoutMs: 5000, }); return ( Loading profile...}> Loading posts...}> ); } function ProfileDetails({ resource }) { const user = resource.user.read(); return

    {user.name}

    ; } function ProfileTimeline({ isStale, resource }) { const posts = resource.posts.read(); return (
      {posts.map((post) => (
    • {post.text}
    • ))}
    ); } export default App; ================================================ FILE: example_old/App.js ================================================ import Brahmos, { Suspense, lazy, Component } from '../src'; import TodoList from './TodoList'; import UseStateExample from './UseStateExample'; import ContextExample from './context'; import RefsExample from './RefsExample'; import CreatePortalExample from './createPortalExample'; import SVGExample from './SVGExample'; import LazySuspenseExample from './lazySuspenseExample'; import ErrorBoundaryExample from './ErrorBoundaryExample'; function shuffle(array) { array = [...array]; var currentIndex = array.length; var temporaryValue; var randomIndex; // While there remain elements to shuffle... while (currentIndex !== 0) { // Pick a remaining element... randomIndex = Math.floor(Math.random() * currentIndex); currentIndex -= 1; // And swap it with the current element. temporaryValue = array[currentIndex]; array[currentIndex] = array[randomIndex]; array[randomIndex] = temporaryValue; } return array; } export function OldApp() { return (
    {/*

    Todo List

    useState hook example

    */} {/*

    Context api example

    */} {/*

    Refs example

    */} {/*

    SVG Example

    */} {/*

    Lazy and Suspense Example

    */} {/*

    Error Boundary Example

    */} {/** Keep the portal example on last */}

    CreatePortal Example

    ); } function Div() { return
    askjdkajsdks
    ; } function Div2({ length, list }) { return (
      {list.slice(0, length).map((val, idx) => (
    • {val}
    • ))}
    ); } export class AppBase extends Component { state = { name: '', list: [1, 2, 3, 4], }; render() { const { name, list } = this.state; return (
    { this.setState({ name: e.target.value.slice(0, 10) }); Brahmos.unstable_deferredUpdates(() => { this.setState({ list: shuffle(list) }, null); }); // this.deferredSetState({ list: shuffle(list) }, null); }} />

    Hello {name}

    {'Hello World'} {name &&
    }
    ); } } export default function App() { return (
    ); } ================================================ FILE: example_old/ErrorBoundaryExample.js ================================================ import Brahmos from '../src'; class ErrorBoundary extends Brahmos.Component { constructor(props) { super(props); this.state = { error: null, errorInfo: null }; } static getDerivedStateFromError(error) { return { error }; } componentDidCatch(error, errorInfo) { // Catch errors in any components below and re-render with error message this.setState({ error: error, errorInfo: errorInfo, }); // You can also log error messages to an error reporting service here } render() { if (this.state.error) { // Error path return (

    Something went wrong.

    {this.state.error && this.state.error.toString()}
    {this.state.errorInfo && this.state.errorInfo.componentStack}
    ); } // Normally, just render children return this.props.children; } } class BuggyCounter extends Brahmos.Component { constructor(props) { super(props); this.state = { counter: 0 }; this.handleClick = this.handleClick.bind(this); } handleClick() { this.setState(({ counter }) => ({ counter: counter + 1, })); } render() { if (this.state.counter === 5) { // Simulate a JS error throw new Error('I crashed!'); } return

    {this.state.counter}

    ; } } export default function ErrorBoundaryExample() { return (

    This is an example of error boundaries in React 16.

    Click on the numbers to increase the counters.
    The counter is programmed to throw when it reaches 5. This simulates a JavaScript error in a component.


    These two counters are inside the same error boundary. If one crashes, the error boundary will replace both of them.


    These two counters are each inside of their own error boundary. So if one crashes, the other is not affected.

    ); } ================================================ FILE: example_old/RefsExample.js ================================================ import Brahmos, { Component, createRef, forwardRef } from '../src'; class Child extends Component { logSomething() { // console.log('something'); } render() { return
    Hello World!!
    ; } } const ChildWithForwardedRef = forwardRef((props, ref) => { return
    Forwarded Ref
    ; }); export default class RefsExample extends Component { constructor() { super(); this.childCreateRef = createRef(); this.domCreateRef = createRef(); this.forwardedRef = createRef(); } logRefs = () => { console.log(this.childCreateRef); console.log(this.childCallbackRef); console.log(this.domCreateRef); console.log(this.domCbRef); console.log(this.forwardedRef); }; render() { return (
    { this.childCallbackRef = instance; }} />
    Dom create ref
    { this.domCbRef = elm; }} > Dom callback ref
    ); } } ================================================ FILE: example_old/SVGExample.js ================================================ import Brahmos, { Component } from '../src'; function Path() { return ( ); } function Image() { return ( ); } function Chart({ data }) { return ( {data.map((item, i) => ( ))} ); } class SVGExample extends Component { state = { data: [99, 44, 11, 55, 33, 115, 4], }; _shuffuleArray(array) { var j, temp, i; for (i = array.length; i; i--) { j = Math.floor(Math.random() * i); temp = array[i - 1]; array[i - 1] = array[j]; array[j] = temp; } return array; } shuffule = () => this.setState({ data: this._shuffuleArray(this.state.data) }); render() { const { data } = this.state; return (
    ); } } export default SVGExample; ================================================ FILE: example_old/TodoList.js ================================================ import Brahmos, { Component } from '../src'; export default class TodoList extends Component { static defaultProps = { maxCount: 5, }; state = { todos: [], text: '' }; setText = (e) => { this.setState({ text: e.target.value }); }; addTodo = () => { let { todos, text } = this.state; const { maxCount } = this.props; if (!text || maxCount === todos.length) { return; } todos = todos.concat(text); this.setState({ todos, text: '' }); }; componentDidMount() { // console.log('TODO List mounted'); } render() { const { todos, text } = this.state; const { maxCount } = this.props; return (
      {todos.map((todo, idx) => (
    • {todo}
    • ))}
    {maxCount > todos.length && ( You can add {maxCount - todos.length} items to this list )} {maxCount === todos.length && List is Full}
    ); } } ================================================ FILE: example_old/UnMountAtNode.js ================================================ import Brahmos, { Component, unmountComponentAtNode } from '../src'; export default class UnMountAtNode extends Component { removeNode = () => { setTimeout(() => { unmountComponentAtNode(document.getElementById('unmount-node')); }, 1000); }; componentWillUnmount() { // console.log('component will unmount life cycle called!!'); } render() { return (

    {' '} Remove a mounted Brahmos component from the DOM and clean up its event handlers and state. If no component was mounted in the container, calling this function does nothing. Returns true if a component was unmounted and false if there was no component to unmount.{' '}

    ); } } ================================================ FILE: example_old/UseStateExample.js ================================================ import Brahmos, { useState } from '../src'; import friends from './friends.json'; export default function UseStateExample() { const [filterStr, setFilter] = useState(''); const filteredLower = filterStr.toLowerCase(); const filteredFriend = friends.filter(({ name }) => { return name.toLowerCase().startsWith(filteredLower); }); return (
    setFilter(e.target.value)} />
      {filteredFriend.map(({ _id, name }) => { return
    • {name}
    • ; })}
    ); } ================================================ FILE: example_old/concurrentApp.js ================================================ import _Brahmos from '../src'; import data from './data.json'; // Doing this to check performance const Brahmos = _Brahmos; const { Component, PureComponent } = Brahmos; function shuffle(array) { array = [...array]; var currentIndex = array.length; var temporaryValue; var randomIndex; // While there remain elements to shuffle... while (currentIndex !== 0) { // Pick a remaining element... randomIndex = Math.floor(Math.random() * currentIndex); currentIndex -= 1; // And swap it with the current element. temporaryValue = array[currentIndex]; array[currentIndex] = array[randomIndex]; array[randomIndex] = temporaryValue; } return array; } function duplicateData(data, count) { const newData = []; const ln = data.length; for (let i = 0; i < count; i++) { newData.push({ ...data[i % ln], id: i }); } return newData; } class Result extends PureComponent { render() { const { result } = this.props; return (
    {result.full_name} 🌟{result.stargazers_count}

    {result.description}

    ); } } // function Result(props) { // const { result } = props; // return ( //
    //
    // // {result.full_name} // // // 🌟{result.stargazers_count} //
    //

    {result.description}

    //
    // ); // } // export const Result = ({ result }) => { // // let i = 0; // // while (i < 100000000) i++; // return ( //
    //
    // // {result.full_name} // // // 🌟{result.stargazers_count} //
    //

    {result.description}

    //
    // ); // }; const initialTime = performance.now(); export default class App extends Component { state = { results: [] }; stateUpdateTime = initialTime; componentDidMount() { this.setState({ results: duplicateData(data.items, 1000), }); // setInterval(() => { // this.shuffle(); // }, 10000); // document.querySelector('#shuffle-btn').addEventListener('click', this.shuffle); } componentDidUpdate() { console.log(performance.now() - this.stateUpdateTime); } shuffle = () => { // console.log('State update'); this.stateUpdateTime = performance.now(); // this.state.results.reverse(); // this.forceUpdate(); this.setState({ results: shuffle(this.state.results) }); }; clear = () => { this.stateUpdateTime = performance.now(); this.setState({ results: [] }); }; render(props) { const { results, value = '' } = this.state || {}; // let i = 0; // while (i < 10000000) i++; return (

    Examples

    { // console.log('Event update'); this.stateUpdateTime = performance.now(); this.setState({ value: e.target.value.slice(0, 10) }); }} />
    {results.map((result) => ( ))}
    ); } } ================================================ FILE: example_old/context.js ================================================ import Brahmos, { Component, createContext, useContext, useState } from '../src'; const BrahmosContext = createContext('Brahmos'); // context as static property class ContextStaticProperty extends Component { render() { const name = this.context; return
    Hello {name}
    ; } } ContextStaticProperty.contextType = BrahmosContext; function ContextConsumer() { return {(name) =>
    Hello {name}
    }
    ; } function UseContext() { const name = useContext(BrahmosContext); return
    Hello {name}
    ; } export default function ContextExample() { const [name, setName] = useState(); return (
    setName(e.target.value)} />

    ContextConsumer

    ContextStaticProperty

    useContext Hook

    ContextConsumer without Provider

    useContext without Provider

    ); } ================================================ FILE: example_old/createPortalExample.js ================================================ import Brahmos, { Component, createPortal, useState } from '../src'; class Child extends Component { componentWillUnmount() { // console.log('unmounted'); } render() { return
    Hello New Root!
    ; } } function CreatePortalExample() { const [display, setDisplay] = useState(true); return (
    {display && createPortal(, document.querySelector('#another-root'))}
    ); } export default CreatePortalExample; ================================================ FILE: example_old/data.json ================================================ { "total_count": 3782, "incomplete_results": false, "items": [ { "id": 42283287, "node_id": "MDEwOlJlcG9zaXRvcnk0MjI4MzI4Nw==", "name": "preact", "full_name": "preactjs/preact", "private": false, "owner": { "login": "preactjs", "id": 26872990, "node_id": "MDEyOk9yZ2FuaXphdGlvbjI2ODcyOTkw", "avatar_url": "https://avatars3.githubusercontent.com/u/26872990?v=4", "gravatar_id": "", "url": "https://api.github.com/users/preactjs", "html_url": "https://github.com/preactjs", "followers_url": "https://api.github.com/users/preactjs/followers", "following_url": "https://api.github.com/users/preactjs/following{/other_user}", "gists_url": "https://api.github.com/users/preactjs/gists{/gist_id}", "starred_url": "https://api.github.com/users/preactjs/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/preactjs/subscriptions", "organizations_url": "https://api.github.com/users/preactjs/orgs", "repos_url": "https://api.github.com/users/preactjs/repos", "events_url": "https://api.github.com/users/preactjs/events{/privacy}", "received_events_url": "https://api.github.com/users/preactjs/received_events", "type": "Organization", "site_admin": false }, "html_url": "https://github.com/preactjs/preact", "description": "⚛️ Fast 3kB React alternative with the same modern API. Components & Virtual DOM.", "fork": false, "url": "https://api.github.com/repos/preactjs/preact", "forks_url": "https://api.github.com/repos/preactjs/preact/forks", "keys_url": "https://api.github.com/repos/preactjs/preact/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/preactjs/preact/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/preactjs/preact/teams", "hooks_url": "https://api.github.com/repos/preactjs/preact/hooks", "issue_events_url": "https://api.github.com/repos/preactjs/preact/issues/events{/number}", "events_url": "https://api.github.com/repos/preactjs/preact/events", "assignees_url": "https://api.github.com/repos/preactjs/preact/assignees{/user}", "branches_url": "https://api.github.com/repos/preactjs/preact/branches{/branch}", "tags_url": "https://api.github.com/repos/preactjs/preact/tags", "blobs_url": "https://api.github.com/repos/preactjs/preact/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/preactjs/preact/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/preactjs/preact/git/refs{/sha}", "trees_url": "https://api.github.com/repos/preactjs/preact/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/preactjs/preact/statuses/{sha}", "languages_url": "https://api.github.com/repos/preactjs/preact/languages", "stargazers_url": "https://api.github.com/repos/preactjs/preact/stargazers", "contributors_url": "https://api.github.com/repos/preactjs/preact/contributors", "subscribers_url": "https://api.github.com/repos/preactjs/preact/subscribers", "subscription_url": "https://api.github.com/repos/preactjs/preact/subscription", "commits_url": "https://api.github.com/repos/preactjs/preact/commits{/sha}", "git_commits_url": "https://api.github.com/repos/preactjs/preact/git/commits{/sha}", "comments_url": "https://api.github.com/repos/preactjs/preact/comments{/number}", "issue_comment_url": "https://api.github.com/repos/preactjs/preact/issues/comments{/number}", "contents_url": "https://api.github.com/repos/preactjs/preact/contents/{+path}", "compare_url": "https://api.github.com/repos/preactjs/preact/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/preactjs/preact/merges", "archive_url": "https://api.github.com/repos/preactjs/preact/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/preactjs/preact/downloads", "issues_url": "https://api.github.com/repos/preactjs/preact/issues{/number}", "pulls_url": "https://api.github.com/repos/preactjs/preact/pulls{/number}", "milestones_url": "https://api.github.com/repos/preactjs/preact/milestones{/number}", "notifications_url": "https://api.github.com/repos/preactjs/preact/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/preactjs/preact/labels{/name}", "releases_url": "https://api.github.com/repos/preactjs/preact/releases{/id}", "deployments_url": "https://api.github.com/repos/preactjs/preact/deployments", "created_at": "2015-09-11T02:40:18Z", "updated_at": "2020-05-15T04:34:56Z", "pushed_at": "2020-05-14T02:58:48Z", "git_url": "git://github.com/preactjs/preact.git", "ssh_url": "git@github.com:preactjs/preact.git", "clone_url": "https://github.com/preactjs/preact.git", "svn_url": "https://github.com/preactjs/preact", "homepage": "https://preactjs.com", "size": 6574, "stargazers_count": 26215, "watchers_count": 26215, "language": "JavaScript", "has_issues": true, "has_projects": true, "has_downloads": true, "has_wiki": true, "has_pages": true, "forks_count": 1428, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 162, "license": { "key": "mit", "name": "MIT License", "spdx_id": "MIT", "url": "https://api.github.com/licenses/mit", "node_id": "MDc6TGljZW5zZTEz" }, "forks": 1428, "open_issues": 162, "watchers": 26215, "default_branch": "master", "score": 1.0 }, { "id": 91839306, "node_id": "MDEwOlJlcG9zaXRvcnk5MTgzOTMwNg==", "name": "preact-cli", "full_name": "preactjs/preact-cli", "private": false, "owner": { "login": "preactjs", "id": 26872990, "node_id": "MDEyOk9yZ2FuaXphdGlvbjI2ODcyOTkw", "avatar_url": "https://avatars3.githubusercontent.com/u/26872990?v=4", "gravatar_id": "", "url": "https://api.github.com/users/preactjs", "html_url": "https://github.com/preactjs", "followers_url": "https://api.github.com/users/preactjs/followers", "following_url": "https://api.github.com/users/preactjs/following{/other_user}", "gists_url": "https://api.github.com/users/preactjs/gists{/gist_id}", "starred_url": "https://api.github.com/users/preactjs/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/preactjs/subscriptions", "organizations_url": "https://api.github.com/users/preactjs/orgs", "repos_url": "https://api.github.com/users/preactjs/repos", "events_url": "https://api.github.com/users/preactjs/events{/privacy}", "received_events_url": "https://api.github.com/users/preactjs/received_events", "type": "Organization", "site_admin": false }, "html_url": "https://github.com/preactjs/preact-cli", "description": "😺 Your next Preact PWA starts in 30 seconds.", "fork": false, "url": "https://api.github.com/repos/preactjs/preact-cli", "forks_url": "https://api.github.com/repos/preactjs/preact-cli/forks", "keys_url": "https://api.github.com/repos/preactjs/preact-cli/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/preactjs/preact-cli/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/preactjs/preact-cli/teams", "hooks_url": "https://api.github.com/repos/preactjs/preact-cli/hooks", "issue_events_url": "https://api.github.com/repos/preactjs/preact-cli/issues/events{/number}", "events_url": "https://api.github.com/repos/preactjs/preact-cli/events", "assignees_url": "https://api.github.com/repos/preactjs/preact-cli/assignees{/user}", "branches_url": "https://api.github.com/repos/preactjs/preact-cli/branches{/branch}", "tags_url": "https://api.github.com/repos/preactjs/preact-cli/tags", "blobs_url": "https://api.github.com/repos/preactjs/preact-cli/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/preactjs/preact-cli/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/preactjs/preact-cli/git/refs{/sha}", "trees_url": "https://api.github.com/repos/preactjs/preact-cli/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/preactjs/preact-cli/statuses/{sha}", "languages_url": "https://api.github.com/repos/preactjs/preact-cli/languages", "stargazers_url": "https://api.github.com/repos/preactjs/preact-cli/stargazers", "contributors_url": "https://api.github.com/repos/preactjs/preact-cli/contributors", "subscribers_url": "https://api.github.com/repos/preactjs/preact-cli/subscribers", "subscription_url": "https://api.github.com/repos/preactjs/preact-cli/subscription", "commits_url": "https://api.github.com/repos/preactjs/preact-cli/commits{/sha}", "git_commits_url": "https://api.github.com/repos/preactjs/preact-cli/git/commits{/sha}", "comments_url": "https://api.github.com/repos/preactjs/preact-cli/comments{/number}", "issue_comment_url": "https://api.github.com/repos/preactjs/preact-cli/issues/comments{/number}", "contents_url": "https://api.github.com/repos/preactjs/preact-cli/contents/{+path}", "compare_url": "https://api.github.com/repos/preactjs/preact-cli/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/preactjs/preact-cli/merges", "archive_url": "https://api.github.com/repos/preactjs/preact-cli/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/preactjs/preact-cli/downloads", "issues_url": "https://api.github.com/repos/preactjs/preact-cli/issues{/number}", "pulls_url": "https://api.github.com/repos/preactjs/preact-cli/pulls{/number}", "milestones_url": "https://api.github.com/repos/preactjs/preact-cli/milestones{/number}", "notifications_url": "https://api.github.com/repos/preactjs/preact-cli/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/preactjs/preact-cli/labels{/name}", "releases_url": "https://api.github.com/repos/preactjs/preact-cli/releases{/id}", "deployments_url": "https://api.github.com/repos/preactjs/preact-cli/deployments", "created_at": "2017-05-19T19:34:36Z", "updated_at": "2020-05-14T16:27:47Z", "pushed_at": "2020-05-14T15:23:10Z", "git_url": "git://github.com/preactjs/preact-cli.git", "ssh_url": "git@github.com:preactjs/preact-cli.git", "clone_url": "https://github.com/preactjs/preact-cli.git", "svn_url": "https://github.com/preactjs/preact-cli", "homepage": "https://cli-demo-next.preactjs.com", "size": 6479, "stargazers_count": 4143, "watchers_count": 4143, "language": "JavaScript", "has_issues": true, "has_projects": true, "has_downloads": true, "has_wiki": true, "has_pages": false, "forks_count": 344, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 112, "license": { "key": "mit", "name": "MIT License", "spdx_id": "MIT", "url": "https://api.github.com/licenses/mit", "node_id": "MDc6TGljZW5zZTEz" }, "forks": 344, "open_issues": 112, "watchers": 4143, "default_branch": "master", "score": 1.0 }, { "id": 55373335, "node_id": "MDEwOlJlcG9zaXRvcnk1NTM3MzMzNQ==", "name": "preact-www", "full_name": "preactjs/preact-www", "private": false, "owner": { "login": "preactjs", "id": 26872990, "node_id": "MDEyOk9yZ2FuaXphdGlvbjI2ODcyOTkw", "avatar_url": "https://avatars3.githubusercontent.com/u/26872990?v=4", "gravatar_id": "", "url": "https://api.github.com/users/preactjs", "html_url": "https://github.com/preactjs", "followers_url": "https://api.github.com/users/preactjs/followers", "following_url": "https://api.github.com/users/preactjs/following{/other_user}", "gists_url": "https://api.github.com/users/preactjs/gists{/gist_id}", "starred_url": "https://api.github.com/users/preactjs/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/preactjs/subscriptions", "organizations_url": "https://api.github.com/users/preactjs/orgs", "repos_url": "https://api.github.com/users/preactjs/repos", "events_url": "https://api.github.com/users/preactjs/events{/privacy}", "received_events_url": "https://api.github.com/users/preactjs/received_events", "type": "Organization", "site_admin": false }, "html_url": "https://github.com/preactjs/preact-www", "description": ":book: Preact documentation website.", "fork": false, "url": "https://api.github.com/repos/preactjs/preact-www", "forks_url": "https://api.github.com/repos/preactjs/preact-www/forks", "keys_url": "https://api.github.com/repos/preactjs/preact-www/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/preactjs/preact-www/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/preactjs/preact-www/teams", "hooks_url": "https://api.github.com/repos/preactjs/preact-www/hooks", "issue_events_url": "https://api.github.com/repos/preactjs/preact-www/issues/events{/number}", "events_url": "https://api.github.com/repos/preactjs/preact-www/events", "assignees_url": "https://api.github.com/repos/preactjs/preact-www/assignees{/user}", "branches_url": "https://api.github.com/repos/preactjs/preact-www/branches{/branch}", "tags_url": "https://api.github.com/repos/preactjs/preact-www/tags", "blobs_url": "https://api.github.com/repos/preactjs/preact-www/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/preactjs/preact-www/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/preactjs/preact-www/git/refs{/sha}", "trees_url": "https://api.github.com/repos/preactjs/preact-www/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/preactjs/preact-www/statuses/{sha}", "languages_url": "https://api.github.com/repos/preactjs/preact-www/languages", "stargazers_url": "https://api.github.com/repos/preactjs/preact-www/stargazers", "contributors_url": "https://api.github.com/repos/preactjs/preact-www/contributors", "subscribers_url": "https://api.github.com/repos/preactjs/preact-www/subscribers", "subscription_url": "https://api.github.com/repos/preactjs/preact-www/subscription", "commits_url": "https://api.github.com/repos/preactjs/preact-www/commits{/sha}", "git_commits_url": "https://api.github.com/repos/preactjs/preact-www/git/commits{/sha}", "comments_url": "https://api.github.com/repos/preactjs/preact-www/comments{/number}", "issue_comment_url": "https://api.github.com/repos/preactjs/preact-www/issues/comments{/number}", "contents_url": "https://api.github.com/repos/preactjs/preact-www/contents/{+path}", "compare_url": "https://api.github.com/repos/preactjs/preact-www/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/preactjs/preact-www/merges", "archive_url": "https://api.github.com/repos/preactjs/preact-www/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/preactjs/preact-www/downloads", "issues_url": "https://api.github.com/repos/preactjs/preact-www/issues{/number}", "pulls_url": "https://api.github.com/repos/preactjs/preact-www/pulls{/number}", "milestones_url": "https://api.github.com/repos/preactjs/preact-www/milestones{/number}", "notifications_url": "https://api.github.com/repos/preactjs/preact-www/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/preactjs/preact-www/labels{/name}", "releases_url": "https://api.github.com/repos/preactjs/preact-www/releases{/id}", "deployments_url": "https://api.github.com/repos/preactjs/preact-www/deployments", "created_at": "2016-04-03T23:00:51Z", "updated_at": "2020-05-14T21:10:20Z", "pushed_at": "2020-05-14T23:37:26Z", "git_url": "git://github.com/preactjs/preact-www.git", "ssh_url": "git@github.com:preactjs/preact-www.git", "clone_url": "https://github.com/preactjs/preact-www.git", "svn_url": "https://github.com/preactjs/preact-www", "homepage": "https://preactjs.com", "size": 4959, "stargazers_count": 247, "watchers_count": 247, "language": "JavaScript", "has_issues": true, "has_projects": true, "has_downloads": true, "has_wiki": false, "has_pages": false, "forks_count": 342, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 44, "license": { "key": "mit", "name": "MIT License", "spdx_id": "MIT", "url": "https://api.github.com/licenses/mit", "node_id": "MDc6TGljZW5zZTEz" }, "forks": 342, "open_issues": 44, "watchers": 247, "default_branch": "master", "score": 1.0 }, { "id": 49995432, "node_id": "MDEwOlJlcG9zaXRvcnk0OTk5NTQzMg==", "name": "preact-boilerplate", "full_name": "developit/preact-boilerplate", "private": false, "owner": { "login": "developit", "id": 105127, "node_id": "MDQ6VXNlcjEwNTEyNw==", "avatar_url": "https://avatars2.githubusercontent.com/u/105127?v=4", "gravatar_id": "", "url": "https://api.github.com/users/developit", "html_url": "https://github.com/developit", "followers_url": "https://api.github.com/users/developit/followers", "following_url": "https://api.github.com/users/developit/following{/other_user}", "gists_url": "https://api.github.com/users/developit/gists{/gist_id}", "starred_url": "https://api.github.com/users/developit/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/developit/subscriptions", "organizations_url": "https://api.github.com/users/developit/orgs", "repos_url": "https://api.github.com/users/developit/repos", "events_url": "https://api.github.com/users/developit/events{/privacy}", "received_events_url": "https://api.github.com/users/developit/received_events", "type": "User", "site_admin": false }, "html_url": "https://github.com/developit/preact-boilerplate", "description": ":guitar: Ready-to-rock Preact starter project, powered by Webpack.", "fork": false, "url": "https://api.github.com/repos/developit/preact-boilerplate", "forks_url": "https://api.github.com/repos/developit/preact-boilerplate/forks", "keys_url": "https://api.github.com/repos/developit/preact-boilerplate/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/developit/preact-boilerplate/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/developit/preact-boilerplate/teams", "hooks_url": "https://api.github.com/repos/developit/preact-boilerplate/hooks", "issue_events_url": "https://api.github.com/repos/developit/preact-boilerplate/issues/events{/number}", "events_url": "https://api.github.com/repos/developit/preact-boilerplate/events", "assignees_url": "https://api.github.com/repos/developit/preact-boilerplate/assignees{/user}", "branches_url": "https://api.github.com/repos/developit/preact-boilerplate/branches{/branch}", "tags_url": "https://api.github.com/repos/developit/preact-boilerplate/tags", "blobs_url": "https://api.github.com/repos/developit/preact-boilerplate/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/developit/preact-boilerplate/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/developit/preact-boilerplate/git/refs{/sha}", "trees_url": "https://api.github.com/repos/developit/preact-boilerplate/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/developit/preact-boilerplate/statuses/{sha}", "languages_url": "https://api.github.com/repos/developit/preact-boilerplate/languages", "stargazers_url": "https://api.github.com/repos/developit/preact-boilerplate/stargazers", "contributors_url": "https://api.github.com/repos/developit/preact-boilerplate/contributors", "subscribers_url": "https://api.github.com/repos/developit/preact-boilerplate/subscribers", "subscription_url": "https://api.github.com/repos/developit/preact-boilerplate/subscription", "commits_url": "https://api.github.com/repos/developit/preact-boilerplate/commits{/sha}", "git_commits_url": "https://api.github.com/repos/developit/preact-boilerplate/git/commits{/sha}", "comments_url": "https://api.github.com/repos/developit/preact-boilerplate/comments{/number}", "issue_comment_url": "https://api.github.com/repos/developit/preact-boilerplate/issues/comments{/number}", "contents_url": "https://api.github.com/repos/developit/preact-boilerplate/contents/{+path}", "compare_url": "https://api.github.com/repos/developit/preact-boilerplate/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/developit/preact-boilerplate/merges", "archive_url": "https://api.github.com/repos/developit/preact-boilerplate/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/developit/preact-boilerplate/downloads", "issues_url": "https://api.github.com/repos/developit/preact-boilerplate/issues{/number}", "pulls_url": "https://api.github.com/repos/developit/preact-boilerplate/pulls{/number}", "milestones_url": "https://api.github.com/repos/developit/preact-boilerplate/milestones{/number}", "notifications_url": "https://api.github.com/repos/developit/preact-boilerplate/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/developit/preact-boilerplate/labels{/name}", "releases_url": "https://api.github.com/repos/developit/preact-boilerplate/releases{/id}", "deployments_url": "https://api.github.com/repos/developit/preact-boilerplate/deployments", "created_at": "2016-01-20T01:27:33Z", "updated_at": "2020-05-10T04:15:21Z", "pushed_at": "2020-05-13T04:01:56Z", "git_url": "git://github.com/developit/preact-boilerplate.git", "ssh_url": "git@github.com:developit/preact-boilerplate.git", "clone_url": "https://github.com/developit/preact-boilerplate.git", "svn_url": "https://github.com/developit/preact-boilerplate", "homepage": "https://preact-boilerplate.surge.sh", "size": 472, "stargazers_count": 941, "watchers_count": 941, "language": "JavaScript", "has_issues": true, "has_projects": true, "has_downloads": true, "has_wiki": true, "has_pages": false, "forks_count": 193, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 34, "license": null, "forks": 193, "open_issues": 34, "watchers": 941, "default_branch": "master", "score": 1.0 }, { "id": 45793760, "node_id": "MDEwOlJlcG9zaXRvcnk0NTc5Mzc2MA==", "name": "preact-compat", "full_name": "preactjs/preact-compat", "private": false, "owner": { "login": "preactjs", "id": 26872990, "node_id": "MDEyOk9yZ2FuaXphdGlvbjI2ODcyOTkw", "avatar_url": "https://avatars3.githubusercontent.com/u/26872990?v=4", "gravatar_id": "", "url": "https://api.github.com/users/preactjs", "html_url": "https://github.com/preactjs", "followers_url": "https://api.github.com/users/preactjs/followers", "following_url": "https://api.github.com/users/preactjs/following{/other_user}", "gists_url": "https://api.github.com/users/preactjs/gists{/gist_id}", "starred_url": "https://api.github.com/users/preactjs/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/preactjs/subscriptions", "organizations_url": "https://api.github.com/users/preactjs/orgs", "repos_url": "https://api.github.com/users/preactjs/repos", "events_url": "https://api.github.com/users/preactjs/events{/privacy}", "received_events_url": "https://api.github.com/users/preactjs/received_events", "type": "Organization", "site_admin": false }, "html_url": "https://github.com/preactjs/preact-compat", "description": ":raised_hands: React compatibility layer for Preact.", "fork": false, "url": "https://api.github.com/repos/preactjs/preact-compat", "forks_url": "https://api.github.com/repos/preactjs/preact-compat/forks", "keys_url": "https://api.github.com/repos/preactjs/preact-compat/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/preactjs/preact-compat/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/preactjs/preact-compat/teams", "hooks_url": "https://api.github.com/repos/preactjs/preact-compat/hooks", "issue_events_url": "https://api.github.com/repos/preactjs/preact-compat/issues/events{/number}", "events_url": "https://api.github.com/repos/preactjs/preact-compat/events", "assignees_url": "https://api.github.com/repos/preactjs/preact-compat/assignees{/user}", "branches_url": "https://api.github.com/repos/preactjs/preact-compat/branches{/branch}", "tags_url": "https://api.github.com/repos/preactjs/preact-compat/tags", "blobs_url": "https://api.github.com/repos/preactjs/preact-compat/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/preactjs/preact-compat/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/preactjs/preact-compat/git/refs{/sha}", "trees_url": "https://api.github.com/repos/preactjs/preact-compat/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/preactjs/preact-compat/statuses/{sha}", "languages_url": "https://api.github.com/repos/preactjs/preact-compat/languages", "stargazers_url": "https://api.github.com/repos/preactjs/preact-compat/stargazers", "contributors_url": "https://api.github.com/repos/preactjs/preact-compat/contributors", "subscribers_url": "https://api.github.com/repos/preactjs/preact-compat/subscribers", "subscription_url": "https://api.github.com/repos/preactjs/preact-compat/subscription", "commits_url": "https://api.github.com/repos/preactjs/preact-compat/commits{/sha}", "git_commits_url": "https://api.github.com/repos/preactjs/preact-compat/git/commits{/sha}", "comments_url": "https://api.github.com/repos/preactjs/preact-compat/comments{/number}", "issue_comment_url": "https://api.github.com/repos/preactjs/preact-compat/issues/comments{/number}", "contents_url": "https://api.github.com/repos/preactjs/preact-compat/contents/{+path}", "compare_url": "https://api.github.com/repos/preactjs/preact-compat/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/preactjs/preact-compat/merges", "archive_url": "https://api.github.com/repos/preactjs/preact-compat/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/preactjs/preact-compat/downloads", "issues_url": "https://api.github.com/repos/preactjs/preact-compat/issues{/number}", "pulls_url": "https://api.github.com/repos/preactjs/preact-compat/pulls{/number}", "milestones_url": "https://api.github.com/repos/preactjs/preact-compat/milestones{/number}", "notifications_url": "https://api.github.com/repos/preactjs/preact-compat/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/preactjs/preact-compat/labels{/name}", "releases_url": "https://api.github.com/repos/preactjs/preact-compat/releases{/id}", "deployments_url": "https://api.github.com/repos/preactjs/preact-compat/deployments", "created_at": "2015-11-08T18:47:28Z", "updated_at": "2020-05-10T15:45:50Z", "pushed_at": "2020-03-06T12:28:17Z", "git_url": "git://github.com/preactjs/preact-compat.git", "ssh_url": "git@github.com:preactjs/preact-compat.git", "clone_url": "https://github.com/preactjs/preact-compat.git", "svn_url": "https://github.com/preactjs/preact-compat", "homepage": "http://npm.im/preact-compat", "size": 283, "stargazers_count": 875, "watchers_count": 875, "language": "JavaScript", "has_issues": true, "has_projects": true, "has_downloads": true, "has_wiki": true, "has_pages": false, "forks_count": 139, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 47, "license": { "key": "mit", "name": "MIT License", "spdx_id": "MIT", "url": "https://api.github.com/licenses/mit", "node_id": "MDc6TGljZW5zZTEz" }, "forks": 139, "open_issues": 47, "watchers": 875, "default_branch": "master", "score": 1.0 }, { "id": 46458028, "node_id": "MDEwOlJlcG9zaXRvcnk0NjQ1ODAyOA==", "name": "preact-router", "full_name": "preactjs/preact-router", "private": false, "owner": { "login": "preactjs", "id": 26872990, "node_id": "MDEyOk9yZ2FuaXphdGlvbjI2ODcyOTkw", "avatar_url": "https://avatars3.githubusercontent.com/u/26872990?v=4", "gravatar_id": "", "url": "https://api.github.com/users/preactjs", "html_url": "https://github.com/preactjs", "followers_url": "https://api.github.com/users/preactjs/followers", "following_url": "https://api.github.com/users/preactjs/following{/other_user}", "gists_url": "https://api.github.com/users/preactjs/gists{/gist_id}", "starred_url": "https://api.github.com/users/preactjs/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/preactjs/subscriptions", "organizations_url": "https://api.github.com/users/preactjs/orgs", "repos_url": "https://api.github.com/users/preactjs/repos", "events_url": "https://api.github.com/users/preactjs/events{/privacy}", "received_events_url": "https://api.github.com/users/preactjs/received_events", "type": "Organization", "site_admin": false }, "html_url": "https://github.com/preactjs/preact-router", "description": ":earth_americas: URL router for Preact.", "fork": false, "url": "https://api.github.com/repos/preactjs/preact-router", "forks_url": "https://api.github.com/repos/preactjs/preact-router/forks", "keys_url": "https://api.github.com/repos/preactjs/preact-router/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/preactjs/preact-router/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/preactjs/preact-router/teams", "hooks_url": "https://api.github.com/repos/preactjs/preact-router/hooks", "issue_events_url": "https://api.github.com/repos/preactjs/preact-router/issues/events{/number}", "events_url": "https://api.github.com/repos/preactjs/preact-router/events", "assignees_url": "https://api.github.com/repos/preactjs/preact-router/assignees{/user}", "branches_url": "https://api.github.com/repos/preactjs/preact-router/branches{/branch}", "tags_url": "https://api.github.com/repos/preactjs/preact-router/tags", "blobs_url": "https://api.github.com/repos/preactjs/preact-router/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/preactjs/preact-router/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/preactjs/preact-router/git/refs{/sha}", "trees_url": "https://api.github.com/repos/preactjs/preact-router/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/preactjs/preact-router/statuses/{sha}", "languages_url": "https://api.github.com/repos/preactjs/preact-router/languages", "stargazers_url": "https://api.github.com/repos/preactjs/preact-router/stargazers", "contributors_url": "https://api.github.com/repos/preactjs/preact-router/contributors", "subscribers_url": "https://api.github.com/repos/preactjs/preact-router/subscribers", "subscription_url": "https://api.github.com/repos/preactjs/preact-router/subscription", "commits_url": "https://api.github.com/repos/preactjs/preact-router/commits{/sha}", "git_commits_url": "https://api.github.com/repos/preactjs/preact-router/git/commits{/sha}", "comments_url": "https://api.github.com/repos/preactjs/preact-router/comments{/number}", "issue_comment_url": "https://api.github.com/repos/preactjs/preact-router/issues/comments{/number}", "contents_url": "https://api.github.com/repos/preactjs/preact-router/contents/{+path}", "compare_url": "https://api.github.com/repos/preactjs/preact-router/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/preactjs/preact-router/merges", "archive_url": "https://api.github.com/repos/preactjs/preact-router/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/preactjs/preact-router/downloads", "issues_url": "https://api.github.com/repos/preactjs/preact-router/issues{/number}", "pulls_url": "https://api.github.com/repos/preactjs/preact-router/pulls{/number}", "milestones_url": "https://api.github.com/repos/preactjs/preact-router/milestones{/number}", "notifications_url": "https://api.github.com/repos/preactjs/preact-router/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/preactjs/preact-router/labels{/name}", "releases_url": "https://api.github.com/repos/preactjs/preact-router/releases{/id}", "deployments_url": "https://api.github.com/repos/preactjs/preact-router/deployments", "created_at": "2015-11-19T01:04:24Z", "updated_at": "2020-05-14T17:03:55Z", "pushed_at": "2020-04-08T06:49:53Z", "git_url": "git://github.com/preactjs/preact-router.git", "ssh_url": "git@github.com:preactjs/preact-router.git", "clone_url": "https://github.com/preactjs/preact-router.git", "svn_url": "https://github.com/preactjs/preact-router", "homepage": "http://npm.im/preact-router", "size": 626, "stargazers_count": 691, "watchers_count": 691, "language": "JavaScript", "has_issues": true, "has_projects": false, "has_downloads": true, "has_wiki": false, "has_pages": false, "forks_count": 131, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 69, "license": { "key": "mit", "name": "MIT License", "spdx_id": "MIT", "url": "https://api.github.com/licenses/mit", "node_id": "MDc6TGljZW5zZTEz" }, "forks": 131, "open_issues": 69, "watchers": 691, "default_branch": "master", "score": 1.0 }, { "id": 82581631, "node_id": "MDEwOlJlcG9zaXRvcnk4MjU4MTYzMQ==", "name": "preact-material-components", "full_name": "prateekbh/preact-material-components", "private": false, "owner": { "login": "prateekbh", "id": 5960497, "node_id": "MDQ6VXNlcjU5NjA0OTc=", "avatar_url": "https://avatars2.githubusercontent.com/u/5960497?v=4", "gravatar_id": "", "url": "https://api.github.com/users/prateekbh", "html_url": "https://github.com/prateekbh", "followers_url": "https://api.github.com/users/prateekbh/followers", "following_url": "https://api.github.com/users/prateekbh/following{/other_user}", "gists_url": "https://api.github.com/users/prateekbh/gists{/gist_id}", "starred_url": "https://api.github.com/users/prateekbh/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/prateekbh/subscriptions", "organizations_url": "https://api.github.com/users/prateekbh/orgs", "repos_url": "https://api.github.com/users/prateekbh/repos", "events_url": "https://api.github.com/users/prateekbh/events{/privacy}", "received_events_url": "https://api.github.com/users/prateekbh/received_events", "type": "User", "site_admin": false }, "html_url": "https://github.com/prateekbh/preact-material-components", "description": "preact wrapper for \"Material Components for the web\"", "fork": false, "url": "https://api.github.com/repos/prateekbh/preact-material-components", "forks_url": "https://api.github.com/repos/prateekbh/preact-material-components/forks", "keys_url": "https://api.github.com/repos/prateekbh/preact-material-components/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/prateekbh/preact-material-components/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/prateekbh/preact-material-components/teams", "hooks_url": "https://api.github.com/repos/prateekbh/preact-material-components/hooks", "issue_events_url": "https://api.github.com/repos/prateekbh/preact-material-components/issues/events{/number}", "events_url": "https://api.github.com/repos/prateekbh/preact-material-components/events", "assignees_url": "https://api.github.com/repos/prateekbh/preact-material-components/assignees{/user}", "branches_url": "https://api.github.com/repos/prateekbh/preact-material-components/branches{/branch}", "tags_url": "https://api.github.com/repos/prateekbh/preact-material-components/tags", "blobs_url": "https://api.github.com/repos/prateekbh/preact-material-components/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/prateekbh/preact-material-components/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/prateekbh/preact-material-components/git/refs{/sha}", "trees_url": "https://api.github.com/repos/prateekbh/preact-material-components/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/prateekbh/preact-material-components/statuses/{sha}", "languages_url": "https://api.github.com/repos/prateekbh/preact-material-components/languages", "stargazers_url": "https://api.github.com/repos/prateekbh/preact-material-components/stargazers", "contributors_url": "https://api.github.com/repos/prateekbh/preact-material-components/contributors", "subscribers_url": "https://api.github.com/repos/prateekbh/preact-material-components/subscribers", "subscription_url": "https://api.github.com/repos/prateekbh/preact-material-components/subscription", "commits_url": "https://api.github.com/repos/prateekbh/preact-material-components/commits{/sha}", "git_commits_url": "https://api.github.com/repos/prateekbh/preact-material-components/git/commits{/sha}", "comments_url": "https://api.github.com/repos/prateekbh/preact-material-components/comments{/number}", "issue_comment_url": "https://api.github.com/repos/prateekbh/preact-material-components/issues/comments{/number}", "contents_url": "https://api.github.com/repos/prateekbh/preact-material-components/contents/{+path}", "compare_url": "https://api.github.com/repos/prateekbh/preact-material-components/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/prateekbh/preact-material-components/merges", "archive_url": "https://api.github.com/repos/prateekbh/preact-material-components/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/prateekbh/preact-material-components/downloads", "issues_url": "https://api.github.com/repos/prateekbh/preact-material-components/issues{/number}", "pulls_url": "https://api.github.com/repos/prateekbh/preact-material-components/pulls{/number}", "milestones_url": "https://api.github.com/repos/prateekbh/preact-material-components/milestones{/number}", "notifications_url": "https://api.github.com/repos/prateekbh/preact-material-components/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/prateekbh/preact-material-components/labels{/name}", "releases_url": "https://api.github.com/repos/prateekbh/preact-material-components/releases{/id}", "deployments_url": "https://api.github.com/repos/prateekbh/preact-material-components/deployments", "created_at": "2017-02-20T17:01:45Z", "updated_at": "2020-05-05T18:50:59Z", "pushed_at": "2020-04-16T05:07:32Z", "git_url": "git://github.com/prateekbh/preact-material-components.git", "ssh_url": "git@github.com:prateekbh/preact-material-components.git", "clone_url": "https://github.com/prateekbh/preact-material-components.git", "svn_url": "https://github.com/prateekbh/preact-material-components", "homepage": "https://material.preactjs.com", "size": 95695, "stargazers_count": 509, "watchers_count": 509, "language": "TypeScript", "has_issues": true, "has_projects": true, "has_downloads": true, "has_wiki": true, "has_pages": false, "forks_count": 85, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 47, "license": { "key": "mit", "name": "MIT License", "spdx_id": "MIT", "url": "https://api.github.com/licenses/mit", "node_id": "MDc6TGljZW5zZTEz" }, "forks": 85, "open_issues": 47, "watchers": 509, "default_branch": "master", "score": 1.0 }, { "id": 34381195, "node_id": "MDEwOlJlcG9zaXRvcnkzNDM4MTE5NQ==", "name": "jsxstyle", "full_name": "jsxstyle/jsxstyle", "private": false, "owner": { "login": "jsxstyle", "id": 29987079, "node_id": "MDEyOk9yZ2FuaXphdGlvbjI5OTg3MDc5", "avatar_url": "https://avatars0.githubusercontent.com/u/29987079?v=4", "gravatar_id": "", "url": "https://api.github.com/users/jsxstyle", "html_url": "https://github.com/jsxstyle", "followers_url": "https://api.github.com/users/jsxstyle/followers", "following_url": "https://api.github.com/users/jsxstyle/following{/other_user}", "gists_url": "https://api.github.com/users/jsxstyle/gists{/gist_id}", "starred_url": "https://api.github.com/users/jsxstyle/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/jsxstyle/subscriptions", "organizations_url": "https://api.github.com/users/jsxstyle/orgs", "repos_url": "https://api.github.com/users/jsxstyle/repos", "events_url": "https://api.github.com/users/jsxstyle/events{/privacy}", "received_events_url": "https://api.github.com/users/jsxstyle/received_events", "type": "Organization", "site_admin": false }, "html_url": "https://github.com/jsxstyle/jsxstyle", "description": "Inline style system for React and Preact", "fork": false, "url": "https://api.github.com/repos/jsxstyle/jsxstyle", "forks_url": "https://api.github.com/repos/jsxstyle/jsxstyle/forks", "keys_url": "https://api.github.com/repos/jsxstyle/jsxstyle/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/jsxstyle/jsxstyle/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/jsxstyle/jsxstyle/teams", "hooks_url": "https://api.github.com/repos/jsxstyle/jsxstyle/hooks", "issue_events_url": "https://api.github.com/repos/jsxstyle/jsxstyle/issues/events{/number}", "events_url": "https://api.github.com/repos/jsxstyle/jsxstyle/events", "assignees_url": "https://api.github.com/repos/jsxstyle/jsxstyle/assignees{/user}", "branches_url": "https://api.github.com/repos/jsxstyle/jsxstyle/branches{/branch}", "tags_url": "https://api.github.com/repos/jsxstyle/jsxstyle/tags", "blobs_url": "https://api.github.com/repos/jsxstyle/jsxstyle/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/jsxstyle/jsxstyle/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/jsxstyle/jsxstyle/git/refs{/sha}", "trees_url": "https://api.github.com/repos/jsxstyle/jsxstyle/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/jsxstyle/jsxstyle/statuses/{sha}", "languages_url": "https://api.github.com/repos/jsxstyle/jsxstyle/languages", "stargazers_url": "https://api.github.com/repos/jsxstyle/jsxstyle/stargazers", "contributors_url": "https://api.github.com/repos/jsxstyle/jsxstyle/contributors", "subscribers_url": "https://api.github.com/repos/jsxstyle/jsxstyle/subscribers", "subscription_url": "https://api.github.com/repos/jsxstyle/jsxstyle/subscription", "commits_url": "https://api.github.com/repos/jsxstyle/jsxstyle/commits{/sha}", "git_commits_url": "https://api.github.com/repos/jsxstyle/jsxstyle/git/commits{/sha}", "comments_url": "https://api.github.com/repos/jsxstyle/jsxstyle/comments{/number}", "issue_comment_url": "https://api.github.com/repos/jsxstyle/jsxstyle/issues/comments{/number}", "contents_url": "https://api.github.com/repos/jsxstyle/jsxstyle/contents/{+path}", "compare_url": "https://api.github.com/repos/jsxstyle/jsxstyle/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/jsxstyle/jsxstyle/merges", "archive_url": "https://api.github.com/repos/jsxstyle/jsxstyle/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/jsxstyle/jsxstyle/downloads", "issues_url": "https://api.github.com/repos/jsxstyle/jsxstyle/issues{/number}", "pulls_url": "https://api.github.com/repos/jsxstyle/jsxstyle/pulls{/number}", "milestones_url": "https://api.github.com/repos/jsxstyle/jsxstyle/milestones{/number}", "notifications_url": "https://api.github.com/repos/jsxstyle/jsxstyle/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/jsxstyle/jsxstyle/labels{/name}", "releases_url": "https://api.github.com/repos/jsxstyle/jsxstyle/releases{/id}", "deployments_url": "https://api.github.com/repos/jsxstyle/jsxstyle/deployments", "created_at": "2015-04-22T09:22:48Z", "updated_at": "2020-05-05T04:33:24Z", "pushed_at": "2020-05-01T07:17:07Z", "git_url": "git://github.com/jsxstyle/jsxstyle.git", "ssh_url": "git@github.com:jsxstyle/jsxstyle.git", "clone_url": "https://github.com/jsxstyle/jsxstyle.git", "svn_url": "https://github.com/jsxstyle/jsxstyle", "homepage": "", "size": 2481, "stargazers_count": 1920, "watchers_count": 1920, "language": "TypeScript", "has_issues": true, "has_projects": true, "has_downloads": true, "has_wiki": true, "has_pages": true, "forks_count": 59, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 7, "license": { "key": "other", "name": "Other", "spdx_id": "NOASSERTION", "url": null, "node_id": "MDc6TGljZW5zZTA=" }, "forks": 59, "open_issues": 7, "watchers": 1920, "default_branch": "master", "score": 1.0 }, { "id": 113351554, "node_id": "MDEwOlJlcG9zaXRvcnkxMTMzNTE1NTQ=", "name": "unistore", "full_name": "developit/unistore", "private": false, "owner": { "login": "developit", "id": 105127, "node_id": "MDQ6VXNlcjEwNTEyNw==", "avatar_url": "https://avatars2.githubusercontent.com/u/105127?v=4", "gravatar_id": "", "url": "https://api.github.com/users/developit", "html_url": "https://github.com/developit", "followers_url": "https://api.github.com/users/developit/followers", "following_url": "https://api.github.com/users/developit/following{/other_user}", "gists_url": "https://api.github.com/users/developit/gists{/gist_id}", "starred_url": "https://api.github.com/users/developit/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/developit/subscriptions", "organizations_url": "https://api.github.com/users/developit/orgs", "repos_url": "https://api.github.com/users/developit/repos", "events_url": "https://api.github.com/users/developit/events{/privacy}", "received_events_url": "https://api.github.com/users/developit/received_events", "type": "User", "site_admin": false }, "html_url": "https://github.com/developit/unistore", "description": "🌶 350b / 650b state container with component actions for Preact & React", "fork": false, "url": "https://api.github.com/repos/developit/unistore", "forks_url": "https://api.github.com/repos/developit/unistore/forks", "keys_url": "https://api.github.com/repos/developit/unistore/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/developit/unistore/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/developit/unistore/teams", "hooks_url": "https://api.github.com/repos/developit/unistore/hooks", "issue_events_url": "https://api.github.com/repos/developit/unistore/issues/events{/number}", "events_url": "https://api.github.com/repos/developit/unistore/events", "assignees_url": "https://api.github.com/repos/developit/unistore/assignees{/user}", "branches_url": "https://api.github.com/repos/developit/unistore/branches{/branch}", "tags_url": "https://api.github.com/repos/developit/unistore/tags", "blobs_url": "https://api.github.com/repos/developit/unistore/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/developit/unistore/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/developit/unistore/git/refs{/sha}", "trees_url": "https://api.github.com/repos/developit/unistore/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/developit/unistore/statuses/{sha}", "languages_url": "https://api.github.com/repos/developit/unistore/languages", "stargazers_url": "https://api.github.com/repos/developit/unistore/stargazers", "contributors_url": "https://api.github.com/repos/developit/unistore/contributors", "subscribers_url": "https://api.github.com/repos/developit/unistore/subscribers", "subscription_url": "https://api.github.com/repos/developit/unistore/subscription", "commits_url": "https://api.github.com/repos/developit/unistore/commits{/sha}", "git_commits_url": "https://api.github.com/repos/developit/unistore/git/commits{/sha}", "comments_url": "https://api.github.com/repos/developit/unistore/comments{/number}", "issue_comment_url": "https://api.github.com/repos/developit/unistore/issues/comments{/number}", "contents_url": "https://api.github.com/repos/developit/unistore/contents/{+path}", "compare_url": "https://api.github.com/repos/developit/unistore/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/developit/unistore/merges", "archive_url": "https://api.github.com/repos/developit/unistore/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/developit/unistore/downloads", "issues_url": "https://api.github.com/repos/developit/unistore/issues{/number}", "pulls_url": "https://api.github.com/repos/developit/unistore/pulls{/number}", "milestones_url": "https://api.github.com/repos/developit/unistore/milestones{/number}", "notifications_url": "https://api.github.com/repos/developit/unistore/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/developit/unistore/labels{/name}", "releases_url": "https://api.github.com/repos/developit/unistore/releases{/id}", "deployments_url": "https://api.github.com/repos/developit/unistore/deployments", "created_at": "2017-12-06T18:15:34Z", "updated_at": "2020-05-14T22:07:57Z", "pushed_at": "2020-04-16T16:22:33Z", "git_url": "git://github.com/developit/unistore.git", "ssh_url": "git@github.com:developit/unistore.git", "clone_url": "https://github.com/developit/unistore.git", "svn_url": "https://github.com/developit/unistore", "homepage": "https://npm.im/unistore", "size": 178, "stargazers_count": 2687, "watchers_count": 2687, "language": "JavaScript", "has_issues": true, "has_projects": true, "has_downloads": true, "has_wiki": true, "has_pages": false, "forks_count": 161, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 49, "license": null, "forks": 161, "open_issues": 49, "watchers": 2687, "default_branch": "master", "score": 1.0 }, { "id": 77597037, "node_id": "MDEwOlJlcG9zaXRvcnk3NzU5NzAzNw==", "name": "preact-pwa", "full_name": "ezekielchentnik/preact-pwa", "private": false, "owner": { "login": "ezekielchentnik", "id": 3333017, "node_id": "MDQ6VXNlcjMzMzMwMTc=", "avatar_url": "https://avatars2.githubusercontent.com/u/3333017?v=4", "gravatar_id": "", "url": "https://api.github.com/users/ezekielchentnik", "html_url": "https://github.com/ezekielchentnik", "followers_url": "https://api.github.com/users/ezekielchentnik/followers", "following_url": "https://api.github.com/users/ezekielchentnik/following{/other_user}", "gists_url": "https://api.github.com/users/ezekielchentnik/gists{/gist_id}", "starred_url": "https://api.github.com/users/ezekielchentnik/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/ezekielchentnik/subscriptions", "organizations_url": "https://api.github.com/users/ezekielchentnik/orgs", "repos_url": "https://api.github.com/users/ezekielchentnik/repos", "events_url": "https://api.github.com/users/ezekielchentnik/events{/privacy}", "received_events_url": "https://api.github.com/users/ezekielchentnik/received_events", "type": "User", "site_admin": false }, "html_url": "https://github.com/ezekielchentnik/preact-pwa", "description": "Super fast progressive web app with small footprint & minimal dependancies", "fork": false, "url": "https://api.github.com/repos/ezekielchentnik/preact-pwa", "forks_url": "https://api.github.com/repos/ezekielchentnik/preact-pwa/forks", "keys_url": "https://api.github.com/repos/ezekielchentnik/preact-pwa/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/ezekielchentnik/preact-pwa/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/ezekielchentnik/preact-pwa/teams", "hooks_url": "https://api.github.com/repos/ezekielchentnik/preact-pwa/hooks", "issue_events_url": "https://api.github.com/repos/ezekielchentnik/preact-pwa/issues/events{/number}", "events_url": "https://api.github.com/repos/ezekielchentnik/preact-pwa/events", "assignees_url": "https://api.github.com/repos/ezekielchentnik/preact-pwa/assignees{/user}", "branches_url": "https://api.github.com/repos/ezekielchentnik/preact-pwa/branches{/branch}", "tags_url": "https://api.github.com/repos/ezekielchentnik/preact-pwa/tags", "blobs_url": "https://api.github.com/repos/ezekielchentnik/preact-pwa/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/ezekielchentnik/preact-pwa/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/ezekielchentnik/preact-pwa/git/refs{/sha}", "trees_url": "https://api.github.com/repos/ezekielchentnik/preact-pwa/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/ezekielchentnik/preact-pwa/statuses/{sha}", "languages_url": "https://api.github.com/repos/ezekielchentnik/preact-pwa/languages", "stargazers_url": "https://api.github.com/repos/ezekielchentnik/preact-pwa/stargazers", "contributors_url": "https://api.github.com/repos/ezekielchentnik/preact-pwa/contributors", "subscribers_url": "https://api.github.com/repos/ezekielchentnik/preact-pwa/subscribers", "subscription_url": "https://api.github.com/repos/ezekielchentnik/preact-pwa/subscription", "commits_url": "https://api.github.com/repos/ezekielchentnik/preact-pwa/commits{/sha}", "git_commits_url": "https://api.github.com/repos/ezekielchentnik/preact-pwa/git/commits{/sha}", "comments_url": "https://api.github.com/repos/ezekielchentnik/preact-pwa/comments{/number}", "issue_comment_url": "https://api.github.com/repos/ezekielchentnik/preact-pwa/issues/comments{/number}", "contents_url": "https://api.github.com/repos/ezekielchentnik/preact-pwa/contents/{+path}", "compare_url": "https://api.github.com/repos/ezekielchentnik/preact-pwa/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/ezekielchentnik/preact-pwa/merges", "archive_url": "https://api.github.com/repos/ezekielchentnik/preact-pwa/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/ezekielchentnik/preact-pwa/downloads", "issues_url": "https://api.github.com/repos/ezekielchentnik/preact-pwa/issues{/number}", "pulls_url": "https://api.github.com/repos/ezekielchentnik/preact-pwa/pulls{/number}", "milestones_url": "https://api.github.com/repos/ezekielchentnik/preact-pwa/milestones{/number}", "notifications_url": "https://api.github.com/repos/ezekielchentnik/preact-pwa/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/ezekielchentnik/preact-pwa/labels{/name}", "releases_url": "https://api.github.com/repos/ezekielchentnik/preact-pwa/releases{/id}", "deployments_url": "https://api.github.com/repos/ezekielchentnik/preact-pwa/deployments", "created_at": "2016-12-29T08:35:40Z", "updated_at": "2020-03-21T13:39:11Z", "pushed_at": "2017-08-12T02:48:39Z", "git_url": "git://github.com/ezekielchentnik/preact-pwa.git", "ssh_url": "git@github.com:ezekielchentnik/preact-pwa.git", "clone_url": "https://github.com/ezekielchentnik/preact-pwa.git", "svn_url": "https://github.com/ezekielchentnik/preact-pwa", "homepage": "https://preact-pwa-yfxiijbzit.now.sh/", "size": 822, "stargazers_count": 507, "watchers_count": 507, "language": "JavaScript", "has_issues": true, "has_projects": true, "has_downloads": true, "has_wiki": true, "has_pages": false, "forks_count": 48, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 7, "license": { "key": "mit", "name": "MIT License", "spdx_id": "MIT", "url": "https://api.github.com/licenses/mit", "node_id": "MDc6TGljZW5zZTEz" }, "forks": 48, "open_issues": 7, "watchers": 507, "default_branch": "master", "score": 1.0 }, { "id": 44454813, "node_id": "MDEwOlJlcG9zaXRvcnk0NDQ1NDgxMw==", "name": "preact-render-to-string", "full_name": "preactjs/preact-render-to-string", "private": false, "owner": { "login": "preactjs", "id": 26872990, "node_id": "MDEyOk9yZ2FuaXphdGlvbjI2ODcyOTkw", "avatar_url": "https://avatars3.githubusercontent.com/u/26872990?v=4", "gravatar_id": "", "url": "https://api.github.com/users/preactjs", "html_url": "https://github.com/preactjs", "followers_url": "https://api.github.com/users/preactjs/followers", "following_url": "https://api.github.com/users/preactjs/following{/other_user}", "gists_url": "https://api.github.com/users/preactjs/gists{/gist_id}", "starred_url": "https://api.github.com/users/preactjs/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/preactjs/subscriptions", "organizations_url": "https://api.github.com/users/preactjs/orgs", "repos_url": "https://api.github.com/users/preactjs/repos", "events_url": "https://api.github.com/users/preactjs/events{/privacy}", "received_events_url": "https://api.github.com/users/preactjs/received_events", "type": "Organization", "site_admin": false }, "html_url": "https://github.com/preactjs/preact-render-to-string", "description": ":page_facing_up: Universal rendering for Preact: render JSX and Preact components to HTML.", "fork": false, "url": "https://api.github.com/repos/preactjs/preact-render-to-string", "forks_url": "https://api.github.com/repos/preactjs/preact-render-to-string/forks", "keys_url": "https://api.github.com/repos/preactjs/preact-render-to-string/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/preactjs/preact-render-to-string/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/preactjs/preact-render-to-string/teams", "hooks_url": "https://api.github.com/repos/preactjs/preact-render-to-string/hooks", "issue_events_url": "https://api.github.com/repos/preactjs/preact-render-to-string/issues/events{/number}", "events_url": "https://api.github.com/repos/preactjs/preact-render-to-string/events", "assignees_url": "https://api.github.com/repos/preactjs/preact-render-to-string/assignees{/user}", "branches_url": "https://api.github.com/repos/preactjs/preact-render-to-string/branches{/branch}", "tags_url": "https://api.github.com/repos/preactjs/preact-render-to-string/tags", "blobs_url": "https://api.github.com/repos/preactjs/preact-render-to-string/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/preactjs/preact-render-to-string/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/preactjs/preact-render-to-string/git/refs{/sha}", "trees_url": "https://api.github.com/repos/preactjs/preact-render-to-string/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/preactjs/preact-render-to-string/statuses/{sha}", "languages_url": "https://api.github.com/repos/preactjs/preact-render-to-string/languages", "stargazers_url": "https://api.github.com/repos/preactjs/preact-render-to-string/stargazers", "contributors_url": "https://api.github.com/repos/preactjs/preact-render-to-string/contributors", "subscribers_url": "https://api.github.com/repos/preactjs/preact-render-to-string/subscribers", "subscription_url": "https://api.github.com/repos/preactjs/preact-render-to-string/subscription", "commits_url": "https://api.github.com/repos/preactjs/preact-render-to-string/commits{/sha}", "git_commits_url": "https://api.github.com/repos/preactjs/preact-render-to-string/git/commits{/sha}", "comments_url": "https://api.github.com/repos/preactjs/preact-render-to-string/comments{/number}", "issue_comment_url": "https://api.github.com/repos/preactjs/preact-render-to-string/issues/comments{/number}", "contents_url": "https://api.github.com/repos/preactjs/preact-render-to-string/contents/{+path}", "compare_url": "https://api.github.com/repos/preactjs/preact-render-to-string/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/preactjs/preact-render-to-string/merges", "archive_url": "https://api.github.com/repos/preactjs/preact-render-to-string/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/preactjs/preact-render-to-string/downloads", "issues_url": "https://api.github.com/repos/preactjs/preact-render-to-string/issues{/number}", "pulls_url": "https://api.github.com/repos/preactjs/preact-render-to-string/pulls{/number}", "milestones_url": "https://api.github.com/repos/preactjs/preact-render-to-string/milestones{/number}", "notifications_url": "https://api.github.com/repos/preactjs/preact-render-to-string/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/preactjs/preact-render-to-string/labels{/name}", "releases_url": "https://api.github.com/repos/preactjs/preact-render-to-string/releases{/id}", "deployments_url": "https://api.github.com/repos/preactjs/preact-render-to-string/deployments", "created_at": "2015-10-17T21:12:30Z", "updated_at": "2020-05-14T11:07:51Z", "pushed_at": "2020-05-08T12:19:47Z", "git_url": "git://github.com/preactjs/preact-render-to-string.git", "ssh_url": "git@github.com:preactjs/preact-render-to-string.git", "clone_url": "https://github.com/preactjs/preact-render-to-string.git", "svn_url": "https://github.com/preactjs/preact-render-to-string", "homepage": "http://npm.im/preact-render-to-string", "size": 289, "stargazers_count": 350, "watchers_count": 350, "language": "JavaScript", "has_issues": true, "has_projects": true, "has_downloads": true, "has_wiki": true, "has_pages": false, "forks_count": 68, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 24, "license": { "key": "mit", "name": "MIT License", "spdx_id": "MIT", "url": "https://api.github.com/licenses/mit", "node_id": "MDc6TGljZW5zZTEz" }, "forks": 68, "open_issues": 24, "watchers": 350, "default_branch": "master", "score": 1.0 }, { "id": 51235098, "node_id": "MDEwOlJlcG9zaXRvcnk1MTIzNTA5OA==", "name": "preact-redux-example", "full_name": "developit/preact-redux-example", "private": false, "owner": { "login": "developit", "id": 105127, "node_id": "MDQ6VXNlcjEwNTEyNw==", "avatar_url": "https://avatars2.githubusercontent.com/u/105127?v=4", "gravatar_id": "", "url": "https://api.github.com/users/developit", "html_url": "https://github.com/developit", "followers_url": "https://api.github.com/users/developit/followers", "following_url": "https://api.github.com/users/developit/following{/other_user}", "gists_url": "https://api.github.com/users/developit/gists{/gist_id}", "starred_url": "https://api.github.com/users/developit/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/developit/subscriptions", "organizations_url": "https://api.github.com/users/developit/orgs", "repos_url": "https://api.github.com/users/developit/repos", "events_url": "https://api.github.com/users/developit/events{/privacy}", "received_events_url": "https://api.github.com/users/developit/received_events", "type": "User", "site_admin": false }, "html_url": "https://github.com/developit/preact-redux-example", "description": ":repeat: Preact + Redux Example Project", "fork": false, "url": "https://api.github.com/repos/developit/preact-redux-example", "forks_url": "https://api.github.com/repos/developit/preact-redux-example/forks", "keys_url": "https://api.github.com/repos/developit/preact-redux-example/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/developit/preact-redux-example/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/developit/preact-redux-example/teams", "hooks_url": "https://api.github.com/repos/developit/preact-redux-example/hooks", "issue_events_url": "https://api.github.com/repos/developit/preact-redux-example/issues/events{/number}", "events_url": "https://api.github.com/repos/developit/preact-redux-example/events", "assignees_url": "https://api.github.com/repos/developit/preact-redux-example/assignees{/user}", "branches_url": "https://api.github.com/repos/developit/preact-redux-example/branches{/branch}", "tags_url": "https://api.github.com/repos/developit/preact-redux-example/tags", "blobs_url": "https://api.github.com/repos/developit/preact-redux-example/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/developit/preact-redux-example/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/developit/preact-redux-example/git/refs{/sha}", "trees_url": "https://api.github.com/repos/developit/preact-redux-example/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/developit/preact-redux-example/statuses/{sha}", "languages_url": "https://api.github.com/repos/developit/preact-redux-example/languages", "stargazers_url": "https://api.github.com/repos/developit/preact-redux-example/stargazers", "contributors_url": "https://api.github.com/repos/developit/preact-redux-example/contributors", "subscribers_url": "https://api.github.com/repos/developit/preact-redux-example/subscribers", "subscription_url": "https://api.github.com/repos/developit/preact-redux-example/subscription", "commits_url": "https://api.github.com/repos/developit/preact-redux-example/commits{/sha}", "git_commits_url": "https://api.github.com/repos/developit/preact-redux-example/git/commits{/sha}", "comments_url": "https://api.github.com/repos/developit/preact-redux-example/comments{/number}", "issue_comment_url": "https://api.github.com/repos/developit/preact-redux-example/issues/comments{/number}", "contents_url": "https://api.github.com/repos/developit/preact-redux-example/contents/{+path}", "compare_url": "https://api.github.com/repos/developit/preact-redux-example/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/developit/preact-redux-example/merges", "archive_url": "https://api.github.com/repos/developit/preact-redux-example/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/developit/preact-redux-example/downloads", "issues_url": "https://api.github.com/repos/developit/preact-redux-example/issues{/number}", "pulls_url": "https://api.github.com/repos/developit/preact-redux-example/pulls{/number}", "milestones_url": "https://api.github.com/repos/developit/preact-redux-example/milestones{/number}", "notifications_url": "https://api.github.com/repos/developit/preact-redux-example/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/developit/preact-redux-example/labels{/name}", "releases_url": "https://api.github.com/repos/developit/preact-redux-example/releases{/id}", "deployments_url": "https://api.github.com/repos/developit/preact-redux-example/deployments", "created_at": "2016-02-07T04:19:06Z", "updated_at": "2020-05-06T07:30:08Z", "pushed_at": "2019-05-21T04:49:58Z", "git_url": "git://github.com/developit/preact-redux-example.git", "ssh_url": "git@github.com:developit/preact-redux-example.git", "clone_url": "https://github.com/developit/preact-redux-example.git", "svn_url": "https://github.com/developit/preact-redux-example", "homepage": "https://preact-redux-example.surge.sh", "size": 24, "stargazers_count": 198, "watchers_count": 198, "language": "JavaScript", "has_issues": true, "has_projects": true, "has_downloads": true, "has_wiki": true, "has_pages": false, "forks_count": 56, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 6, "license": null, "forks": 56, "open_issues": 6, "watchers": 198, "default_branch": "master", "score": 1.0 }, { "id": 79582090, "node_id": "MDEwOlJlcG9zaXRvcnk3OTU4MjA5MA==", "name": "awesome-preact", "full_name": "preactjs/awesome-preact", "private": false, "owner": { "login": "preactjs", "id": 26872990, "node_id": "MDEyOk9yZ2FuaXphdGlvbjI2ODcyOTkw", "avatar_url": "https://avatars3.githubusercontent.com/u/26872990?v=4", "gravatar_id": "", "url": "https://api.github.com/users/preactjs", "html_url": "https://github.com/preactjs", "followers_url": "https://api.github.com/users/preactjs/followers", "following_url": "https://api.github.com/users/preactjs/following{/other_user}", "gists_url": "https://api.github.com/users/preactjs/gists{/gist_id}", "starred_url": "https://api.github.com/users/preactjs/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/preactjs/subscriptions", "organizations_url": "https://api.github.com/users/preactjs/orgs", "repos_url": "https://api.github.com/users/preactjs/repos", "events_url": "https://api.github.com/users/preactjs/events{/privacy}", "received_events_url": "https://api.github.com/users/preactjs/received_events", "type": "Organization", "site_admin": false }, "html_url": "https://github.com/preactjs/awesome-preact", "description": "A curated list of amazingly awesome things regarding Preact ecosystem :star2:", "fork": false, "url": "https://api.github.com/repos/preactjs/awesome-preact", "forks_url": "https://api.github.com/repos/preactjs/awesome-preact/forks", "keys_url": "https://api.github.com/repos/preactjs/awesome-preact/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/preactjs/awesome-preact/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/preactjs/awesome-preact/teams", "hooks_url": "https://api.github.com/repos/preactjs/awesome-preact/hooks", "issue_events_url": "https://api.github.com/repos/preactjs/awesome-preact/issues/events{/number}", "events_url": "https://api.github.com/repos/preactjs/awesome-preact/events", "assignees_url": "https://api.github.com/repos/preactjs/awesome-preact/assignees{/user}", "branches_url": "https://api.github.com/repos/preactjs/awesome-preact/branches{/branch}", "tags_url": "https://api.github.com/repos/preactjs/awesome-preact/tags", "blobs_url": "https://api.github.com/repos/preactjs/awesome-preact/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/preactjs/awesome-preact/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/preactjs/awesome-preact/git/refs{/sha}", "trees_url": "https://api.github.com/repos/preactjs/awesome-preact/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/preactjs/awesome-preact/statuses/{sha}", "languages_url": "https://api.github.com/repos/preactjs/awesome-preact/languages", "stargazers_url": "https://api.github.com/repos/preactjs/awesome-preact/stargazers", "contributors_url": "https://api.github.com/repos/preactjs/awesome-preact/contributors", "subscribers_url": "https://api.github.com/repos/preactjs/awesome-preact/subscribers", "subscription_url": "https://api.github.com/repos/preactjs/awesome-preact/subscription", "commits_url": "https://api.github.com/repos/preactjs/awesome-preact/commits{/sha}", "git_commits_url": "https://api.github.com/repos/preactjs/awesome-preact/git/commits{/sha}", "comments_url": "https://api.github.com/repos/preactjs/awesome-preact/comments{/number}", "issue_comment_url": "https://api.github.com/repos/preactjs/awesome-preact/issues/comments{/number}", "contents_url": "https://api.github.com/repos/preactjs/awesome-preact/contents/{+path}", "compare_url": "https://api.github.com/repos/preactjs/awesome-preact/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/preactjs/awesome-preact/merges", "archive_url": "https://api.github.com/repos/preactjs/awesome-preact/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/preactjs/awesome-preact/downloads", "issues_url": "https://api.github.com/repos/preactjs/awesome-preact/issues{/number}", "pulls_url": "https://api.github.com/repos/preactjs/awesome-preact/pulls{/number}", "milestones_url": "https://api.github.com/repos/preactjs/awesome-preact/milestones{/number}", "notifications_url": "https://api.github.com/repos/preactjs/awesome-preact/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/preactjs/awesome-preact/labels{/name}", "releases_url": "https://api.github.com/repos/preactjs/awesome-preact/releases{/id}", "deployments_url": "https://api.github.com/repos/preactjs/awesome-preact/deployments", "created_at": "2017-01-20T17:44:25Z", "updated_at": "2020-05-14T15:55:40Z", "pushed_at": "2020-05-11T19:18:22Z", "git_url": "git://github.com/preactjs/awesome-preact.git", "ssh_url": "git@github.com:preactjs/awesome-preact.git", "clone_url": "https://github.com/preactjs/awesome-preact.git", "svn_url": "https://github.com/preactjs/awesome-preact", "homepage": null, "size": 107, "stargazers_count": 415, "watchers_count": 415, "language": null, "has_issues": true, "has_projects": true, "has_downloads": true, "has_wiki": true, "has_pages": false, "forks_count": 36, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 1, "license": null, "forks": 36, "open_issues": 1, "watchers": 415, "default_branch": "master", "score": 1.0 }, { "id": 78304781, "node_id": "MDEwOlJlcG9zaXRvcnk3ODMwNDc4MQ==", "name": "preact-starter", "full_name": "lukeed/preact-starter", "private": false, "owner": { "login": "lukeed", "id": 5855893, "node_id": "MDQ6VXNlcjU4NTU4OTM=", "avatar_url": "https://avatars1.githubusercontent.com/u/5855893?v=4", "gravatar_id": "", "url": "https://api.github.com/users/lukeed", "html_url": "https://github.com/lukeed", "followers_url": "https://api.github.com/users/lukeed/followers", "following_url": "https://api.github.com/users/lukeed/following{/other_user}", "gists_url": "https://api.github.com/users/lukeed/gists{/gist_id}", "starred_url": "https://api.github.com/users/lukeed/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/lukeed/subscriptions", "organizations_url": "https://api.github.com/users/lukeed/orgs", "repos_url": "https://api.github.com/users/lukeed/repos", "events_url": "https://api.github.com/users/lukeed/events{/privacy}", "received_events_url": "https://api.github.com/users/lukeed/received_events", "type": "User", "site_admin": false }, "html_url": "https://github.com/lukeed/preact-starter", "description": "Webpack3 boilerplate for building SPA / PWA / offline front-end apps with Preact", "fork": false, "url": "https://api.github.com/repos/lukeed/preact-starter", "forks_url": "https://api.github.com/repos/lukeed/preact-starter/forks", "keys_url": "https://api.github.com/repos/lukeed/preact-starter/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/lukeed/preact-starter/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/lukeed/preact-starter/teams", "hooks_url": "https://api.github.com/repos/lukeed/preact-starter/hooks", "issue_events_url": "https://api.github.com/repos/lukeed/preact-starter/issues/events{/number}", "events_url": "https://api.github.com/repos/lukeed/preact-starter/events", "assignees_url": "https://api.github.com/repos/lukeed/preact-starter/assignees{/user}", "branches_url": "https://api.github.com/repos/lukeed/preact-starter/branches{/branch}", "tags_url": "https://api.github.com/repos/lukeed/preact-starter/tags", "blobs_url": "https://api.github.com/repos/lukeed/preact-starter/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/lukeed/preact-starter/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/lukeed/preact-starter/git/refs{/sha}", "trees_url": "https://api.github.com/repos/lukeed/preact-starter/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/lukeed/preact-starter/statuses/{sha}", "languages_url": "https://api.github.com/repos/lukeed/preact-starter/languages", "stargazers_url": "https://api.github.com/repos/lukeed/preact-starter/stargazers", "contributors_url": "https://api.github.com/repos/lukeed/preact-starter/contributors", "subscribers_url": "https://api.github.com/repos/lukeed/preact-starter/subscribers", "subscription_url": "https://api.github.com/repos/lukeed/preact-starter/subscription", "commits_url": "https://api.github.com/repos/lukeed/preact-starter/commits{/sha}", "git_commits_url": "https://api.github.com/repos/lukeed/preact-starter/git/commits{/sha}", "comments_url": "https://api.github.com/repos/lukeed/preact-starter/comments{/number}", "issue_comment_url": "https://api.github.com/repos/lukeed/preact-starter/issues/comments{/number}", "contents_url": "https://api.github.com/repos/lukeed/preact-starter/contents/{+path}", "compare_url": "https://api.github.com/repos/lukeed/preact-starter/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/lukeed/preact-starter/merges", "archive_url": "https://api.github.com/repos/lukeed/preact-starter/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/lukeed/preact-starter/downloads", "issues_url": "https://api.github.com/repos/lukeed/preact-starter/issues{/number}", "pulls_url": "https://api.github.com/repos/lukeed/preact-starter/pulls{/number}", "milestones_url": "https://api.github.com/repos/lukeed/preact-starter/milestones{/number}", "notifications_url": "https://api.github.com/repos/lukeed/preact-starter/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/lukeed/preact-starter/labels{/name}", "releases_url": "https://api.github.com/repos/lukeed/preact-starter/releases{/id}", "deployments_url": "https://api.github.com/repos/lukeed/preact-starter/deployments", "created_at": "2017-01-07T21:28:51Z", "updated_at": "2020-05-04T11:01:01Z", "pushed_at": "2018-11-23T18:48:47Z", "git_url": "git://github.com/lukeed/preact-starter.git", "ssh_url": "git@github.com:lukeed/preact-starter.git", "clone_url": "https://github.com/lukeed/preact-starter.git", "svn_url": "https://github.com/lukeed/preact-starter", "homepage": "https://preact-starter.now.sh/", "size": 698, "stargazers_count": 382, "watchers_count": 382, "language": "JavaScript", "has_issues": true, "has_projects": false, "has_downloads": true, "has_wiki": false, "has_pages": false, "forks_count": 34, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 2, "license": { "key": "mit", "name": "MIT License", "spdx_id": "MIT", "url": "https://api.github.com/licenses/mit", "node_id": "MDc6TGljZW5zZTEz" }, "forks": 34, "open_issues": 2, "watchers": 382, "default_branch": "master", "score": 1.0 }, { "id": 79223894, "node_id": "MDEwOlJlcG9zaXRvcnk3OTIyMzg5NA==", "name": "preact-habitat", "full_name": "zouhir/preact-habitat", "private": false, "owner": { "login": "zouhir", "id": 5052316, "node_id": "MDQ6VXNlcjUwNTIzMTY=", "avatar_url": "https://avatars3.githubusercontent.com/u/5052316?v=4", "gravatar_id": "", "url": "https://api.github.com/users/zouhir", "html_url": "https://github.com/zouhir", "followers_url": "https://api.github.com/users/zouhir/followers", "following_url": "https://api.github.com/users/zouhir/following{/other_user}", "gists_url": "https://api.github.com/users/zouhir/gists{/gist_id}", "starred_url": "https://api.github.com/users/zouhir/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/zouhir/subscriptions", "organizations_url": "https://api.github.com/users/zouhir/orgs", "repos_url": "https://api.github.com/users/zouhir/repos", "events_url": "https://api.github.com/users/zouhir/events{/privacy}", "received_events_url": "https://api.github.com/users/zouhir/received_events", "type": "User", "site_admin": false }, "html_url": "https://github.com/zouhir/preact-habitat", "description": "Zero configuration Preact widgets renderer in any host DOM", "fork": false, "url": "https://api.github.com/repos/zouhir/preact-habitat", "forks_url": "https://api.github.com/repos/zouhir/preact-habitat/forks", "keys_url": "https://api.github.com/repos/zouhir/preact-habitat/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/zouhir/preact-habitat/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/zouhir/preact-habitat/teams", "hooks_url": "https://api.github.com/repos/zouhir/preact-habitat/hooks", "issue_events_url": "https://api.github.com/repos/zouhir/preact-habitat/issues/events{/number}", "events_url": "https://api.github.com/repos/zouhir/preact-habitat/events", "assignees_url": "https://api.github.com/repos/zouhir/preact-habitat/assignees{/user}", "branches_url": "https://api.github.com/repos/zouhir/preact-habitat/branches{/branch}", "tags_url": "https://api.github.com/repos/zouhir/preact-habitat/tags", "blobs_url": "https://api.github.com/repos/zouhir/preact-habitat/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/zouhir/preact-habitat/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/zouhir/preact-habitat/git/refs{/sha}", "trees_url": "https://api.github.com/repos/zouhir/preact-habitat/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/zouhir/preact-habitat/statuses/{sha}", "languages_url": "https://api.github.com/repos/zouhir/preact-habitat/languages", "stargazers_url": "https://api.github.com/repos/zouhir/preact-habitat/stargazers", "contributors_url": "https://api.github.com/repos/zouhir/preact-habitat/contributors", "subscribers_url": "https://api.github.com/repos/zouhir/preact-habitat/subscribers", "subscription_url": "https://api.github.com/repos/zouhir/preact-habitat/subscription", "commits_url": "https://api.github.com/repos/zouhir/preact-habitat/commits{/sha}", "git_commits_url": "https://api.github.com/repos/zouhir/preact-habitat/git/commits{/sha}", "comments_url": "https://api.github.com/repos/zouhir/preact-habitat/comments{/number}", "issue_comment_url": "https://api.github.com/repos/zouhir/preact-habitat/issues/comments{/number}", "contents_url": "https://api.github.com/repos/zouhir/preact-habitat/contents/{+path}", "compare_url": "https://api.github.com/repos/zouhir/preact-habitat/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/zouhir/preact-habitat/merges", "archive_url": "https://api.github.com/repos/zouhir/preact-habitat/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/zouhir/preact-habitat/downloads", "issues_url": "https://api.github.com/repos/zouhir/preact-habitat/issues{/number}", "pulls_url": "https://api.github.com/repos/zouhir/preact-habitat/pulls{/number}", "milestones_url": "https://api.github.com/repos/zouhir/preact-habitat/milestones{/number}", "notifications_url": "https://api.github.com/repos/zouhir/preact-habitat/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/zouhir/preact-habitat/labels{/name}", "releases_url": "https://api.github.com/repos/zouhir/preact-habitat/releases{/id}", "deployments_url": "https://api.github.com/repos/zouhir/preact-habitat/deployments", "created_at": "2017-01-17T12:08:35Z", "updated_at": "2020-05-11T05:49:46Z", "pushed_at": "2019-08-09T15:22:17Z", "git_url": "git://github.com/zouhir/preact-habitat.git", "ssh_url": "git@github.com:zouhir/preact-habitat.git", "clone_url": "https://github.com/zouhir/preact-habitat.git", "svn_url": "https://github.com/zouhir/preact-habitat", "homepage": "", "size": 1467, "stargazers_count": 399, "watchers_count": 399, "language": "JavaScript", "has_issues": true, "has_projects": false, "has_downloads": true, "has_wiki": false, "has_pages": false, "forks_count": 31, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 11, "license": { "key": "mit", "name": "MIT License", "spdx_id": "MIT", "url": "https://api.github.com/licenses/mit", "node_id": "MDc6TGljZW5zZTEz" }, "forks": 31, "open_issues": 11, "watchers": 399, "default_branch": "master", "score": 1.0 }, { "id": 97216102, "node_id": "MDEwOlJlcG9zaXRvcnk5NzIxNjEwMg==", "name": "gitalk", "full_name": "gitalk/gitalk", "private": false, "owner": { "login": "gitalk", "id": 29697133, "node_id": "MDEyOk9yZ2FuaXphdGlvbjI5Njk3MTMz", "avatar_url": "https://avatars1.githubusercontent.com/u/29697133?v=4", "gravatar_id": "", "url": "https://api.github.com/users/gitalk", "html_url": "https://github.com/gitalk", "followers_url": "https://api.github.com/users/gitalk/followers", "following_url": "https://api.github.com/users/gitalk/following{/other_user}", "gists_url": "https://api.github.com/users/gitalk/gists{/gist_id}", "starred_url": "https://api.github.com/users/gitalk/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/gitalk/subscriptions", "organizations_url": "https://api.github.com/users/gitalk/orgs", "repos_url": "https://api.github.com/users/gitalk/repos", "events_url": "https://api.github.com/users/gitalk/events{/privacy}", "received_events_url": "https://api.github.com/users/gitalk/received_events", "type": "Organization", "site_admin": false }, "html_url": "https://github.com/gitalk/gitalk", "description": "Gitalk is a modern comment component based on Github Issue and Preact.", "fork": false, "url": "https://api.github.com/repos/gitalk/gitalk", "forks_url": "https://api.github.com/repos/gitalk/gitalk/forks", "keys_url": "https://api.github.com/repos/gitalk/gitalk/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/gitalk/gitalk/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/gitalk/gitalk/teams", "hooks_url": "https://api.github.com/repos/gitalk/gitalk/hooks", "issue_events_url": "https://api.github.com/repos/gitalk/gitalk/issues/events{/number}", "events_url": "https://api.github.com/repos/gitalk/gitalk/events", "assignees_url": "https://api.github.com/repos/gitalk/gitalk/assignees{/user}", "branches_url": "https://api.github.com/repos/gitalk/gitalk/branches{/branch}", "tags_url": "https://api.github.com/repos/gitalk/gitalk/tags", "blobs_url": "https://api.github.com/repos/gitalk/gitalk/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/gitalk/gitalk/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/gitalk/gitalk/git/refs{/sha}", "trees_url": "https://api.github.com/repos/gitalk/gitalk/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/gitalk/gitalk/statuses/{sha}", "languages_url": "https://api.github.com/repos/gitalk/gitalk/languages", "stargazers_url": "https://api.github.com/repos/gitalk/gitalk/stargazers", "contributors_url": "https://api.github.com/repos/gitalk/gitalk/contributors", "subscribers_url": "https://api.github.com/repos/gitalk/gitalk/subscribers", "subscription_url": "https://api.github.com/repos/gitalk/gitalk/subscription", "commits_url": "https://api.github.com/repos/gitalk/gitalk/commits{/sha}", "git_commits_url": "https://api.github.com/repos/gitalk/gitalk/git/commits{/sha}", "comments_url": "https://api.github.com/repos/gitalk/gitalk/comments{/number}", "issue_comment_url": "https://api.github.com/repos/gitalk/gitalk/issues/comments{/number}", "contents_url": "https://api.github.com/repos/gitalk/gitalk/contents/{+path}", "compare_url": "https://api.github.com/repos/gitalk/gitalk/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/gitalk/gitalk/merges", "archive_url": "https://api.github.com/repos/gitalk/gitalk/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/gitalk/gitalk/downloads", "issues_url": "https://api.github.com/repos/gitalk/gitalk/issues{/number}", "pulls_url": "https://api.github.com/repos/gitalk/gitalk/pulls{/number}", "milestones_url": "https://api.github.com/repos/gitalk/gitalk/milestones{/number}", "notifications_url": "https://api.github.com/repos/gitalk/gitalk/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/gitalk/gitalk/labels{/name}", "releases_url": "https://api.github.com/repos/gitalk/gitalk/releases{/id}", "deployments_url": "https://api.github.com/repos/gitalk/gitalk/deployments", "created_at": "2017-07-14T09:16:22Z", "updated_at": "2020-05-15T01:23:42Z", "pushed_at": "2020-05-13T13:38:00Z", "git_url": "git://github.com/gitalk/gitalk.git", "ssh_url": "git@github.com:gitalk/gitalk.git", "clone_url": "https://github.com/gitalk/gitalk.git", "svn_url": "https://github.com/gitalk/gitalk", "homepage": "https://gitalk.github.io", "size": 3109, "stargazers_count": 4356, "watchers_count": 4356, "language": "JavaScript", "has_issues": true, "has_projects": true, "has_downloads": true, "has_wiki": true, "has_pages": false, "forks_count": 434, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 80, "license": { "key": "mit", "name": "MIT License", "spdx_id": "MIT", "url": "https://api.github.com/licenses/mit", "node_id": "MDc6TGljZW5zZTEz" }, "forks": 434, "open_issues": 80, "watchers": 4356, "default_branch": "master", "score": 1.0 }, { "id": 74236755, "node_id": "MDEwOlJlcG9zaXRvcnk3NDIzNjc1NQ==", "name": "fathom", "full_name": "usefathom/fathom", "private": false, "owner": { "login": "usefathom", "id": 38684088, "node_id": "MDEyOk9yZ2FuaXphdGlvbjM4Njg0MDg4", "avatar_url": "https://avatars0.githubusercontent.com/u/38684088?v=4", "gravatar_id": "", "url": "https://api.github.com/users/usefathom", "html_url": "https://github.com/usefathom", "followers_url": "https://api.github.com/users/usefathom/followers", "following_url": "https://api.github.com/users/usefathom/following{/other_user}", "gists_url": "https://api.github.com/users/usefathom/gists{/gist_id}", "starred_url": "https://api.github.com/users/usefathom/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/usefathom/subscriptions", "organizations_url": "https://api.github.com/users/usefathom/orgs", "repos_url": "https://api.github.com/users/usefathom/repos", "events_url": "https://api.github.com/users/usefathom/events{/privacy}", "received_events_url": "https://api.github.com/users/usefathom/received_events", "type": "Organization", "site_admin": false }, "html_url": "https://github.com/usefathom/fathom", "description": "Fathom Lite. Simple, privacy-focused website analytics. Built with Golang & Preact.", "fork": false, "url": "https://api.github.com/repos/usefathom/fathom", "forks_url": "https://api.github.com/repos/usefathom/fathom/forks", "keys_url": "https://api.github.com/repos/usefathom/fathom/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/usefathom/fathom/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/usefathom/fathom/teams", "hooks_url": "https://api.github.com/repos/usefathom/fathom/hooks", "issue_events_url": "https://api.github.com/repos/usefathom/fathom/issues/events{/number}", "events_url": "https://api.github.com/repos/usefathom/fathom/events", "assignees_url": "https://api.github.com/repos/usefathom/fathom/assignees{/user}", "branches_url": "https://api.github.com/repos/usefathom/fathom/branches{/branch}", "tags_url": "https://api.github.com/repos/usefathom/fathom/tags", "blobs_url": "https://api.github.com/repos/usefathom/fathom/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/usefathom/fathom/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/usefathom/fathom/git/refs{/sha}", "trees_url": "https://api.github.com/repos/usefathom/fathom/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/usefathom/fathom/statuses/{sha}", "languages_url": "https://api.github.com/repos/usefathom/fathom/languages", "stargazers_url": "https://api.github.com/repos/usefathom/fathom/stargazers", "contributors_url": "https://api.github.com/repos/usefathom/fathom/contributors", "subscribers_url": "https://api.github.com/repos/usefathom/fathom/subscribers", "subscription_url": "https://api.github.com/repos/usefathom/fathom/subscription", "commits_url": "https://api.github.com/repos/usefathom/fathom/commits{/sha}", "git_commits_url": "https://api.github.com/repos/usefathom/fathom/git/commits{/sha}", "comments_url": "https://api.github.com/repos/usefathom/fathom/comments{/number}", "issue_comment_url": "https://api.github.com/repos/usefathom/fathom/issues/comments{/number}", "contents_url": "https://api.github.com/repos/usefathom/fathom/contents/{+path}", "compare_url": "https://api.github.com/repos/usefathom/fathom/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/usefathom/fathom/merges", "archive_url": "https://api.github.com/repos/usefathom/fathom/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/usefathom/fathom/downloads", "issues_url": "https://api.github.com/repos/usefathom/fathom/issues{/number}", "pulls_url": "https://api.github.com/repos/usefathom/fathom/pulls{/number}", "milestones_url": "https://api.github.com/repos/usefathom/fathom/milestones{/number}", "notifications_url": "https://api.github.com/repos/usefathom/fathom/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/usefathom/fathom/labels{/name}", "releases_url": "https://api.github.com/repos/usefathom/fathom/releases{/id}", "deployments_url": "https://api.github.com/repos/usefathom/fathom/deployments", "created_at": "2016-11-19T21:34:03Z", "updated_at": "2020-05-14T20:00:22Z", "pushed_at": "2020-05-08T05:38:09Z", "git_url": "git://github.com/usefathom/fathom.git", "ssh_url": "git@github.com:usefathom/fathom.git", "clone_url": "https://github.com/usefathom/fathom.git", "svn_url": "https://github.com/usefathom/fathom", "homepage": "https://usefathom.com/", "size": 14212, "stargazers_count": 6461, "watchers_count": 6461, "language": "Go", "has_issues": true, "has_projects": false, "has_downloads": true, "has_wiki": false, "has_pages": false, "forks_count": 283, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 40, "license": { "key": "mit", "name": "MIT License", "spdx_id": "MIT", "url": "https://api.github.com/licenses/mit", "node_id": "MDc6TGljZW5zZTEz" }, "forks": 283, "open_issues": 40, "watchers": 6461, "default_branch": "master", "score": 1.0 }, { "id": 84757332, "node_id": "MDEwOlJlcG9zaXRvcnk4NDc1NzMzMg==", "name": "preact-hn", "full_name": "kristoferbaxter/preact-hn", "private": false, "owner": { "login": "kristoferbaxter", "id": 61764, "node_id": "MDQ6VXNlcjYxNzY0", "avatar_url": "https://avatars1.githubusercontent.com/u/61764?v=4", "gravatar_id": "", "url": "https://api.github.com/users/kristoferbaxter", "html_url": "https://github.com/kristoferbaxter", "followers_url": "https://api.github.com/users/kristoferbaxter/followers", "following_url": "https://api.github.com/users/kristoferbaxter/following{/other_user}", "gists_url": "https://api.github.com/users/kristoferbaxter/gists{/gist_id}", "starred_url": "https://api.github.com/users/kristoferbaxter/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/kristoferbaxter/subscriptions", "organizations_url": "https://api.github.com/users/kristoferbaxter/orgs", "repos_url": "https://api.github.com/users/kristoferbaxter/repos", "events_url": "https://api.github.com/users/kristoferbaxter/events{/privacy}", "received_events_url": "https://api.github.com/users/kristoferbaxter/received_events", "type": "User", "site_admin": false }, "html_url": "https://github.com/kristoferbaxter/preact-hn", "description": "Demonstration of Preact used to build Hacker News as a PWA.", "fork": false, "url": "https://api.github.com/repos/kristoferbaxter/preact-hn", "forks_url": "https://api.github.com/repos/kristoferbaxter/preact-hn/forks", "keys_url": "https://api.github.com/repos/kristoferbaxter/preact-hn/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/kristoferbaxter/preact-hn/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/kristoferbaxter/preact-hn/teams", "hooks_url": "https://api.github.com/repos/kristoferbaxter/preact-hn/hooks", "issue_events_url": "https://api.github.com/repos/kristoferbaxter/preact-hn/issues/events{/number}", "events_url": "https://api.github.com/repos/kristoferbaxter/preact-hn/events", "assignees_url": "https://api.github.com/repos/kristoferbaxter/preact-hn/assignees{/user}", "branches_url": "https://api.github.com/repos/kristoferbaxter/preact-hn/branches{/branch}", "tags_url": "https://api.github.com/repos/kristoferbaxter/preact-hn/tags", "blobs_url": "https://api.github.com/repos/kristoferbaxter/preact-hn/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/kristoferbaxter/preact-hn/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/kristoferbaxter/preact-hn/git/refs{/sha}", "trees_url": "https://api.github.com/repos/kristoferbaxter/preact-hn/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/kristoferbaxter/preact-hn/statuses/{sha}", "languages_url": "https://api.github.com/repos/kristoferbaxter/preact-hn/languages", "stargazers_url": "https://api.github.com/repos/kristoferbaxter/preact-hn/stargazers", "contributors_url": "https://api.github.com/repos/kristoferbaxter/preact-hn/contributors", "subscribers_url": "https://api.github.com/repos/kristoferbaxter/preact-hn/subscribers", "subscription_url": "https://api.github.com/repos/kristoferbaxter/preact-hn/subscription", "commits_url": "https://api.github.com/repos/kristoferbaxter/preact-hn/commits{/sha}", "git_commits_url": "https://api.github.com/repos/kristoferbaxter/preact-hn/git/commits{/sha}", "comments_url": "https://api.github.com/repos/kristoferbaxter/preact-hn/comments{/number}", "issue_comment_url": "https://api.github.com/repos/kristoferbaxter/preact-hn/issues/comments{/number}", "contents_url": "https://api.github.com/repos/kristoferbaxter/preact-hn/contents/{+path}", "compare_url": "https://api.github.com/repos/kristoferbaxter/preact-hn/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/kristoferbaxter/preact-hn/merges", "archive_url": "https://api.github.com/repos/kristoferbaxter/preact-hn/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/kristoferbaxter/preact-hn/downloads", "issues_url": "https://api.github.com/repos/kristoferbaxter/preact-hn/issues{/number}", "pulls_url": "https://api.github.com/repos/kristoferbaxter/preact-hn/pulls{/number}", "milestones_url": "https://api.github.com/repos/kristoferbaxter/preact-hn/milestones{/number}", "notifications_url": "https://api.github.com/repos/kristoferbaxter/preact-hn/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/kristoferbaxter/preact-hn/labels{/name}", "releases_url": "https://api.github.com/repos/kristoferbaxter/preact-hn/releases{/id}", "deployments_url": "https://api.github.com/repos/kristoferbaxter/preact-hn/deployments", "created_at": "2017-03-12T21:05:15Z", "updated_at": "2020-05-13T13:37:50Z", "pushed_at": "2018-04-16T16:48:15Z", "git_url": "git://github.com/kristoferbaxter/preact-hn.git", "ssh_url": "git@github.com:kristoferbaxter/preact-hn.git", "clone_url": "https://github.com/kristoferbaxter/preact-hn.git", "svn_url": "https://github.com/kristoferbaxter/preact-hn", "homepage": null, "size": 399, "stargazers_count": 301, "watchers_count": 301, "language": "TypeScript", "has_issues": true, "has_projects": false, "has_downloads": true, "has_wiki": false, "has_pages": false, "forks_count": 30, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 10, "license": { "key": "mit", "name": "MIT License", "spdx_id": "MIT", "url": "https://api.github.com/licenses/mit", "node_id": "MDc6TGljZW5zZTEz" }, "forks": 30, "open_issues": 10, "watchers": 301, "default_branch": "master", "score": 1.0 }, { "id": 60229688, "node_id": "MDEwOlJlcG9zaXRvcnk2MDIyOTY4OA==", "name": "preact-redux", "full_name": "developit/preact-redux", "private": false, "owner": { "login": "developit", "id": 105127, "node_id": "MDQ6VXNlcjEwNTEyNw==", "avatar_url": "https://avatars2.githubusercontent.com/u/105127?v=4", "gravatar_id": "", "url": "https://api.github.com/users/developit", "html_url": "https://github.com/developit", "followers_url": "https://api.github.com/users/developit/followers", "following_url": "https://api.github.com/users/developit/following{/other_user}", "gists_url": "https://api.github.com/users/developit/gists{/gist_id}", "starred_url": "https://api.github.com/users/developit/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/developit/subscriptions", "organizations_url": "https://api.github.com/users/developit/orgs", "repos_url": "https://api.github.com/users/developit/repos", "events_url": "https://api.github.com/users/developit/events{/privacy}", "received_events_url": "https://api.github.com/users/developit/received_events", "type": "User", "site_admin": false }, "html_url": "https://github.com/developit/preact-redux", "description": ":loop: Preact integration for Redux (no shim needed!)", "fork": false, "url": "https://api.github.com/repos/developit/preact-redux", "forks_url": "https://api.github.com/repos/developit/preact-redux/forks", "keys_url": "https://api.github.com/repos/developit/preact-redux/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/developit/preact-redux/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/developit/preact-redux/teams", "hooks_url": "https://api.github.com/repos/developit/preact-redux/hooks", "issue_events_url": "https://api.github.com/repos/developit/preact-redux/issues/events{/number}", "events_url": "https://api.github.com/repos/developit/preact-redux/events", "assignees_url": "https://api.github.com/repos/developit/preact-redux/assignees{/user}", "branches_url": "https://api.github.com/repos/developit/preact-redux/branches{/branch}", "tags_url": "https://api.github.com/repos/developit/preact-redux/tags", "blobs_url": "https://api.github.com/repos/developit/preact-redux/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/developit/preact-redux/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/developit/preact-redux/git/refs{/sha}", "trees_url": "https://api.github.com/repos/developit/preact-redux/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/developit/preact-redux/statuses/{sha}", "languages_url": "https://api.github.com/repos/developit/preact-redux/languages", "stargazers_url": "https://api.github.com/repos/developit/preact-redux/stargazers", "contributors_url": "https://api.github.com/repos/developit/preact-redux/contributors", "subscribers_url": "https://api.github.com/repos/developit/preact-redux/subscribers", "subscription_url": "https://api.github.com/repos/developit/preact-redux/subscription", "commits_url": "https://api.github.com/repos/developit/preact-redux/commits{/sha}", "git_commits_url": "https://api.github.com/repos/developit/preact-redux/git/commits{/sha}", "comments_url": "https://api.github.com/repos/developit/preact-redux/comments{/number}", "issue_comment_url": "https://api.github.com/repos/developit/preact-redux/issues/comments{/number}", "contents_url": "https://api.github.com/repos/developit/preact-redux/contents/{+path}", "compare_url": "https://api.github.com/repos/developit/preact-redux/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/developit/preact-redux/merges", "archive_url": "https://api.github.com/repos/developit/preact-redux/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/developit/preact-redux/downloads", "issues_url": "https://api.github.com/repos/developit/preact-redux/issues{/number}", "pulls_url": "https://api.github.com/repos/developit/preact-redux/pulls{/number}", "milestones_url": "https://api.github.com/repos/developit/preact-redux/milestones{/number}", "notifications_url": "https://api.github.com/repos/developit/preact-redux/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/developit/preact-redux/labels{/name}", "releases_url": "https://api.github.com/repos/developit/preact-redux/releases{/id}", "deployments_url": "https://api.github.com/repos/developit/preact-redux/deployments", "created_at": "2016-06-02T03:28:15Z", "updated_at": "2020-05-02T14:21:42Z", "pushed_at": "2020-01-21T20:57:08Z", "git_url": "git://github.com/developit/preact-redux.git", "ssh_url": "git@github.com:developit/preact-redux.git", "clone_url": "https://github.com/developit/preact-redux.git", "svn_url": "https://github.com/developit/preact-redux", "homepage": "https://npm.im/preact-redux", "size": 292, "stargazers_count": 275, "watchers_count": 275, "language": "JavaScript", "has_issues": true, "has_projects": true, "has_downloads": true, "has_wiki": true, "has_pages": false, "forks_count": 29, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 16, "license": { "key": "mit", "name": "MIT License", "spdx_id": "MIT", "url": "https://api.github.com/licenses/mit", "node_id": "MDc6TGljZW5zZTEz" }, "forks": 29, "open_issues": 16, "watchers": 275, "default_branch": "master", "score": 1.0 }, { "id": 144045551, "node_id": "MDEwOlJlcG9zaXRvcnkxNDQwNDU1NTE=", "name": "Rocket.Chat.Livechat", "full_name": "RocketChat/Rocket.Chat.Livechat", "private": false, "owner": { "login": "RocketChat", "id": 12508788, "node_id": "MDEyOk9yZ2FuaXphdGlvbjEyNTA4Nzg4", "avatar_url": "https://avatars1.githubusercontent.com/u/12508788?v=4", "gravatar_id": "", "url": "https://api.github.com/users/RocketChat", "html_url": "https://github.com/RocketChat", "followers_url": "https://api.github.com/users/RocketChat/followers", "following_url": "https://api.github.com/users/RocketChat/following{/other_user}", "gists_url": "https://api.github.com/users/RocketChat/gists{/gist_id}", "starred_url": "https://api.github.com/users/RocketChat/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/RocketChat/subscriptions", "organizations_url": "https://api.github.com/users/RocketChat/orgs", "repos_url": "https://api.github.com/users/RocketChat/repos", "events_url": "https://api.github.com/users/RocketChat/events{/privacy}", "received_events_url": "https://api.github.com/users/RocketChat/received_events", "type": "Organization", "site_admin": false }, "html_url": "https://github.com/RocketChat/Rocket.Chat.Livechat", "description": "New Livechat client written in Preact", "fork": false, "url": "https://api.github.com/repos/RocketChat/Rocket.Chat.Livechat", "forks_url": "https://api.github.com/repos/RocketChat/Rocket.Chat.Livechat/forks", "keys_url": "https://api.github.com/repos/RocketChat/Rocket.Chat.Livechat/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/RocketChat/Rocket.Chat.Livechat/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/RocketChat/Rocket.Chat.Livechat/teams", "hooks_url": "https://api.github.com/repos/RocketChat/Rocket.Chat.Livechat/hooks", "issue_events_url": "https://api.github.com/repos/RocketChat/Rocket.Chat.Livechat/issues/events{/number}", "events_url": "https://api.github.com/repos/RocketChat/Rocket.Chat.Livechat/events", "assignees_url": "https://api.github.com/repos/RocketChat/Rocket.Chat.Livechat/assignees{/user}", "branches_url": "https://api.github.com/repos/RocketChat/Rocket.Chat.Livechat/branches{/branch}", "tags_url": "https://api.github.com/repos/RocketChat/Rocket.Chat.Livechat/tags", "blobs_url": "https://api.github.com/repos/RocketChat/Rocket.Chat.Livechat/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/RocketChat/Rocket.Chat.Livechat/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/RocketChat/Rocket.Chat.Livechat/git/refs{/sha}", "trees_url": "https://api.github.com/repos/RocketChat/Rocket.Chat.Livechat/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/RocketChat/Rocket.Chat.Livechat/statuses/{sha}", "languages_url": "https://api.github.com/repos/RocketChat/Rocket.Chat.Livechat/languages", "stargazers_url": "https://api.github.com/repos/RocketChat/Rocket.Chat.Livechat/stargazers", "contributors_url": "https://api.github.com/repos/RocketChat/Rocket.Chat.Livechat/contributors", "subscribers_url": "https://api.github.com/repos/RocketChat/Rocket.Chat.Livechat/subscribers", "subscription_url": "https://api.github.com/repos/RocketChat/Rocket.Chat.Livechat/subscription", "commits_url": "https://api.github.com/repos/RocketChat/Rocket.Chat.Livechat/commits{/sha}", "git_commits_url": "https://api.github.com/repos/RocketChat/Rocket.Chat.Livechat/git/commits{/sha}", "comments_url": "https://api.github.com/repos/RocketChat/Rocket.Chat.Livechat/comments{/number}", "issue_comment_url": "https://api.github.com/repos/RocketChat/Rocket.Chat.Livechat/issues/comments{/number}", "contents_url": "https://api.github.com/repos/RocketChat/Rocket.Chat.Livechat/contents/{+path}", "compare_url": "https://api.github.com/repos/RocketChat/Rocket.Chat.Livechat/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/RocketChat/Rocket.Chat.Livechat/merges", "archive_url": "https://api.github.com/repos/RocketChat/Rocket.Chat.Livechat/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/RocketChat/Rocket.Chat.Livechat/downloads", "issues_url": "https://api.github.com/repos/RocketChat/Rocket.Chat.Livechat/issues{/number}", "pulls_url": "https://api.github.com/repos/RocketChat/Rocket.Chat.Livechat/pulls{/number}", "milestones_url": "https://api.github.com/repos/RocketChat/Rocket.Chat.Livechat/milestones{/number}", "notifications_url": "https://api.github.com/repos/RocketChat/Rocket.Chat.Livechat/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/RocketChat/Rocket.Chat.Livechat/labels{/name}", "releases_url": "https://api.github.com/repos/RocketChat/Rocket.Chat.Livechat/releases{/id}", "deployments_url": "https://api.github.com/repos/RocketChat/Rocket.Chat.Livechat/deployments", "created_at": "2018-08-08T17:18:37Z", "updated_at": "2020-05-12T16:10:17Z", "pushed_at": "2020-05-15T01:18:25Z", "git_url": "git://github.com/RocketChat/Rocket.Chat.Livechat.git", "ssh_url": "git@github.com:RocketChat/Rocket.Chat.Livechat.git", "clone_url": "https://github.com/RocketChat/Rocket.Chat.Livechat.git", "svn_url": "https://github.com/RocketChat/Rocket.Chat.Livechat", "homepage": "https://rocket.chat/Rocket.Chat.Livechat", "size": 98221, "stargazers_count": 113, "watchers_count": 113, "language": "JavaScript", "has_issues": true, "has_projects": true, "has_downloads": true, "has_wiki": true, "has_pages": true, "forks_count": 77, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 100, "license": { "key": "mit", "name": "MIT License", "spdx_id": "MIT", "url": "https://api.github.com/licenses/mit", "node_id": "MDc6TGljZW5zZTEz" }, "forks": 77, "open_issues": 100, "watchers": 113, "default_branch": "develop", "score": 1.0 }, { "id": 93886655, "node_id": "MDEwOlJlcG9zaXRvcnk5Mzg4NjY1NQ==", "name": "preact-i18n", "full_name": "synacor/preact-i18n", "private": false, "owner": { "login": "synacor", "id": 5131423, "node_id": "MDEyOk9yZ2FuaXphdGlvbjUxMzE0MjM=", "avatar_url": "https://avatars0.githubusercontent.com/u/5131423?v=4", "gravatar_id": "", "url": "https://api.github.com/users/synacor", "html_url": "https://github.com/synacor", "followers_url": "https://api.github.com/users/synacor/followers", "following_url": "https://api.github.com/users/synacor/following{/other_user}", "gists_url": "https://api.github.com/users/synacor/gists{/gist_id}", "starred_url": "https://api.github.com/users/synacor/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/synacor/subscriptions", "organizations_url": "https://api.github.com/users/synacor/orgs", "repos_url": "https://api.github.com/users/synacor/repos", "events_url": "https://api.github.com/users/synacor/events{/privacy}", "received_events_url": "https://api.github.com/users/synacor/received_events", "type": "Organization", "site_admin": false }, "html_url": "https://github.com/synacor/preact-i18n", "description": "Simple localization for Preact.", "fork": false, "url": "https://api.github.com/repos/synacor/preact-i18n", "forks_url": "https://api.github.com/repos/synacor/preact-i18n/forks", "keys_url": "https://api.github.com/repos/synacor/preact-i18n/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/synacor/preact-i18n/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/synacor/preact-i18n/teams", "hooks_url": "https://api.github.com/repos/synacor/preact-i18n/hooks", "issue_events_url": "https://api.github.com/repos/synacor/preact-i18n/issues/events{/number}", "events_url": "https://api.github.com/repos/synacor/preact-i18n/events", "assignees_url": "https://api.github.com/repos/synacor/preact-i18n/assignees{/user}", "branches_url": "https://api.github.com/repos/synacor/preact-i18n/branches{/branch}", "tags_url": "https://api.github.com/repos/synacor/preact-i18n/tags", "blobs_url": "https://api.github.com/repos/synacor/preact-i18n/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/synacor/preact-i18n/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/synacor/preact-i18n/git/refs{/sha}", "trees_url": "https://api.github.com/repos/synacor/preact-i18n/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/synacor/preact-i18n/statuses/{sha}", "languages_url": "https://api.github.com/repos/synacor/preact-i18n/languages", "stargazers_url": "https://api.github.com/repos/synacor/preact-i18n/stargazers", "contributors_url": "https://api.github.com/repos/synacor/preact-i18n/contributors", "subscribers_url": "https://api.github.com/repos/synacor/preact-i18n/subscribers", "subscription_url": "https://api.github.com/repos/synacor/preact-i18n/subscription", "commits_url": "https://api.github.com/repos/synacor/preact-i18n/commits{/sha}", "git_commits_url": "https://api.github.com/repos/synacor/preact-i18n/git/commits{/sha}", "comments_url": "https://api.github.com/repos/synacor/preact-i18n/comments{/number}", "issue_comment_url": "https://api.github.com/repos/synacor/preact-i18n/issues/comments{/number}", "contents_url": "https://api.github.com/repos/synacor/preact-i18n/contents/{+path}", "compare_url": "https://api.github.com/repos/synacor/preact-i18n/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/synacor/preact-i18n/merges", "archive_url": "https://api.github.com/repos/synacor/preact-i18n/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/synacor/preact-i18n/downloads", "issues_url": "https://api.github.com/repos/synacor/preact-i18n/issues{/number}", "pulls_url": "https://api.github.com/repos/synacor/preact-i18n/pulls{/number}", "milestones_url": "https://api.github.com/repos/synacor/preact-i18n/milestones{/number}", "notifications_url": "https://api.github.com/repos/synacor/preact-i18n/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/synacor/preact-i18n/labels{/name}", "releases_url": "https://api.github.com/repos/synacor/preact-i18n/releases{/id}", "deployments_url": "https://api.github.com/repos/synacor/preact-i18n/deployments", "created_at": "2017-06-09T18:32:11Z", "updated_at": "2020-04-29T13:20:54Z", "pushed_at": "2020-05-05T21:14:41Z", "git_url": "git://github.com/synacor/preact-i18n.git", "ssh_url": "git@github.com:synacor/preact-i18n.git", "clone_url": "https://github.com/synacor/preact-i18n.git", "svn_url": "https://github.com/synacor/preact-i18n", "homepage": null, "size": 233, "stargazers_count": 168, "watchers_count": 168, "language": "JavaScript", "has_issues": true, "has_projects": false, "has_downloads": true, "has_wiki": true, "has_pages": false, "forks_count": 22, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 7, "license": { "key": "bsd-3-clause", "name": "BSD 3-Clause \"New\" or \"Revised\" License", "spdx_id": "BSD-3-Clause", "url": "https://api.github.com/licenses/bsd-3-clause", "node_id": "MDc6TGljZW5zZTU=" }, "forks": 22, "open_issues": 7, "watchers": 168, "default_branch": "master", "score": 1.0 }, { "id": 16177735, "node_id": "MDEwOlJlcG9zaXRvcnkxNjE3NzczNQ==", "name": "redux-websocket-example", "full_name": "maxnachlinger/redux-websocket-example", "private": false, "owner": { "login": "maxnachlinger", "id": 1207210, "node_id": "MDQ6VXNlcjEyMDcyMTA=", "avatar_url": "https://avatars3.githubusercontent.com/u/1207210?v=4", "gravatar_id": "", "url": "https://api.github.com/users/maxnachlinger", "html_url": "https://github.com/maxnachlinger", "followers_url": "https://api.github.com/users/maxnachlinger/followers", "following_url": "https://api.github.com/users/maxnachlinger/following{/other_user}", "gists_url": "https://api.github.com/users/maxnachlinger/gists{/gist_id}", "starred_url": "https://api.github.com/users/maxnachlinger/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/maxnachlinger/subscriptions", "organizations_url": "https://api.github.com/users/maxnachlinger/orgs", "repos_url": "https://api.github.com/users/maxnachlinger/repos", "events_url": "https://api.github.com/users/maxnachlinger/events{/privacy}", "received_events_url": "https://api.github.com/users/maxnachlinger/received_events", "type": "User", "site_admin": false }, "html_url": "https://github.com/maxnachlinger/redux-websocket-example", "description": "Preact/Redux + Websocket Example: For Fun", "fork": false, "url": "https://api.github.com/repos/maxnachlinger/redux-websocket-example", "forks_url": "https://api.github.com/repos/maxnachlinger/redux-websocket-example/forks", "keys_url": "https://api.github.com/repos/maxnachlinger/redux-websocket-example/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/maxnachlinger/redux-websocket-example/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/maxnachlinger/redux-websocket-example/teams", "hooks_url": "https://api.github.com/repos/maxnachlinger/redux-websocket-example/hooks", "issue_events_url": "https://api.github.com/repos/maxnachlinger/redux-websocket-example/issues/events{/number}", "events_url": "https://api.github.com/repos/maxnachlinger/redux-websocket-example/events", "assignees_url": "https://api.github.com/repos/maxnachlinger/redux-websocket-example/assignees{/user}", "branches_url": "https://api.github.com/repos/maxnachlinger/redux-websocket-example/branches{/branch}", "tags_url": "https://api.github.com/repos/maxnachlinger/redux-websocket-example/tags", "blobs_url": "https://api.github.com/repos/maxnachlinger/redux-websocket-example/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/maxnachlinger/redux-websocket-example/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/maxnachlinger/redux-websocket-example/git/refs{/sha}", "trees_url": "https://api.github.com/repos/maxnachlinger/redux-websocket-example/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/maxnachlinger/redux-websocket-example/statuses/{sha}", "languages_url": "https://api.github.com/repos/maxnachlinger/redux-websocket-example/languages", "stargazers_url": "https://api.github.com/repos/maxnachlinger/redux-websocket-example/stargazers", "contributors_url": "https://api.github.com/repos/maxnachlinger/redux-websocket-example/contributors", "subscribers_url": "https://api.github.com/repos/maxnachlinger/redux-websocket-example/subscribers", "subscription_url": "https://api.github.com/repos/maxnachlinger/redux-websocket-example/subscription", "commits_url": "https://api.github.com/repos/maxnachlinger/redux-websocket-example/commits{/sha}", "git_commits_url": "https://api.github.com/repos/maxnachlinger/redux-websocket-example/git/commits{/sha}", "comments_url": "https://api.github.com/repos/maxnachlinger/redux-websocket-example/comments{/number}", "issue_comment_url": "https://api.github.com/repos/maxnachlinger/redux-websocket-example/issues/comments{/number}", "contents_url": "https://api.github.com/repos/maxnachlinger/redux-websocket-example/contents/{+path}", "compare_url": "https://api.github.com/repos/maxnachlinger/redux-websocket-example/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/maxnachlinger/redux-websocket-example/merges", "archive_url": "https://api.github.com/repos/maxnachlinger/redux-websocket-example/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/maxnachlinger/redux-websocket-example/downloads", "issues_url": "https://api.github.com/repos/maxnachlinger/redux-websocket-example/issues{/number}", "pulls_url": "https://api.github.com/repos/maxnachlinger/redux-websocket-example/pulls{/number}", "milestones_url": "https://api.github.com/repos/maxnachlinger/redux-websocket-example/milestones{/number}", "notifications_url": "https://api.github.com/repos/maxnachlinger/redux-websocket-example/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/maxnachlinger/redux-websocket-example/labels{/name}", "releases_url": "https://api.github.com/repos/maxnachlinger/redux-websocket-example/releases{/id}", "deployments_url": "https://api.github.com/repos/maxnachlinger/redux-websocket-example/deployments", "created_at": "2014-01-23T16:18:34Z", "updated_at": "2020-04-20T12:29:23Z", "pushed_at": "2020-01-29T18:47:25Z", "git_url": "git://github.com/maxnachlinger/redux-websocket-example.git", "ssh_url": "git@github.com:maxnachlinger/redux-websocket-example.git", "clone_url": "https://github.com/maxnachlinger/redux-websocket-example.git", "svn_url": "https://github.com/maxnachlinger/redux-websocket-example", "homepage": "", "size": 682, "stargazers_count": 255, "watchers_count": 255, "language": "JavaScript", "has_issues": true, "has_projects": true, "has_downloads": true, "has_wiki": true, "has_pages": false, "forks_count": 44, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 0, "license": null, "forks": 44, "open_issues": 0, "watchers": 255, "default_branch": "master", "score": 1.0 }, { "id": 50541843, "node_id": "MDEwOlJlcG9zaXRvcnk1MDU0MTg0Mw==", "name": "preact-mdl", "full_name": "developit/preact-mdl", "private": false, "owner": { "login": "developit", "id": 105127, "node_id": "MDQ6VXNlcjEwNTEyNw==", "avatar_url": "https://avatars2.githubusercontent.com/u/105127?v=4", "gravatar_id": "", "url": "https://api.github.com/users/developit", "html_url": "https://github.com/developit", "followers_url": "https://api.github.com/users/developit/followers", "following_url": "https://api.github.com/users/developit/following{/other_user}", "gists_url": "https://api.github.com/users/developit/gists{/gist_id}", "starred_url": "https://api.github.com/users/developit/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/developit/subscriptions", "organizations_url": "https://api.github.com/users/developit/orgs", "repos_url": "https://api.github.com/users/developit/repos", "events_url": "https://api.github.com/users/developit/events{/privacy}", "received_events_url": "https://api.github.com/users/developit/received_events", "type": "User", "site_admin": false }, "html_url": "https://github.com/developit/preact-mdl", "description": ":boom: A collection of Preact Components that encapsulate Google's Material Design Lite.", "fork": false, "url": "https://api.github.com/repos/developit/preact-mdl", "forks_url": "https://api.github.com/repos/developit/preact-mdl/forks", "keys_url": "https://api.github.com/repos/developit/preact-mdl/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/developit/preact-mdl/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/developit/preact-mdl/teams", "hooks_url": "https://api.github.com/repos/developit/preact-mdl/hooks", "issue_events_url": "https://api.github.com/repos/developit/preact-mdl/issues/events{/number}", "events_url": "https://api.github.com/repos/developit/preact-mdl/events", "assignees_url": "https://api.github.com/repos/developit/preact-mdl/assignees{/user}", "branches_url": "https://api.github.com/repos/developit/preact-mdl/branches{/branch}", "tags_url": "https://api.github.com/repos/developit/preact-mdl/tags", "blobs_url": "https://api.github.com/repos/developit/preact-mdl/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/developit/preact-mdl/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/developit/preact-mdl/git/refs{/sha}", "trees_url": "https://api.github.com/repos/developit/preact-mdl/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/developit/preact-mdl/statuses/{sha}", "languages_url": "https://api.github.com/repos/developit/preact-mdl/languages", "stargazers_url": "https://api.github.com/repos/developit/preact-mdl/stargazers", "contributors_url": "https://api.github.com/repos/developit/preact-mdl/contributors", "subscribers_url": "https://api.github.com/repos/developit/preact-mdl/subscribers", "subscription_url": "https://api.github.com/repos/developit/preact-mdl/subscription", "commits_url": "https://api.github.com/repos/developit/preact-mdl/commits{/sha}", "git_commits_url": "https://api.github.com/repos/developit/preact-mdl/git/commits{/sha}", "comments_url": "https://api.github.com/repos/developit/preact-mdl/comments{/number}", "issue_comment_url": "https://api.github.com/repos/developit/preact-mdl/issues/comments{/number}", "contents_url": "https://api.github.com/repos/developit/preact-mdl/contents/{+path}", "compare_url": "https://api.github.com/repos/developit/preact-mdl/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/developit/preact-mdl/merges", "archive_url": "https://api.github.com/repos/developit/preact-mdl/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/developit/preact-mdl/downloads", "issues_url": "https://api.github.com/repos/developit/preact-mdl/issues{/number}", "pulls_url": "https://api.github.com/repos/developit/preact-mdl/pulls{/number}", "milestones_url": "https://api.github.com/repos/developit/preact-mdl/milestones{/number}", "notifications_url": "https://api.github.com/repos/developit/preact-mdl/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/developit/preact-mdl/labels{/name}", "releases_url": "https://api.github.com/repos/developit/preact-mdl/releases{/id}", "deployments_url": "https://api.github.com/repos/developit/preact-mdl/deployments", "created_at": "2016-01-27T22:16:37Z", "updated_at": "2020-03-22T21:17:17Z", "pushed_at": "2018-07-18T23:43:09Z", "git_url": "git://github.com/developit/preact-mdl.git", "ssh_url": "git@github.com:developit/preact-mdl.git", "clone_url": "https://github.com/developit/preact-mdl.git", "svn_url": "https://github.com/developit/preact-mdl", "homepage": "", "size": 47, "stargazers_count": 178, "watchers_count": 178, "language": "JavaScript", "has_issues": true, "has_projects": true, "has_downloads": true, "has_wiki": true, "has_pages": false, "forks_count": 27, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 10, "license": { "key": "mit", "name": "MIT License", "spdx_id": "MIT", "url": "https://api.github.com/licenses/mit", "node_id": "MDc6TGljZW5zZTEz" }, "forks": 27, "open_issues": 10, "watchers": 178, "default_branch": "master", "score": 1.0 }, { "id": 81686392, "node_id": "MDEwOlJlcG9zaXRvcnk4MTY4NjM5Mg==", "name": "preact-minimal", "full_name": "aganglada/preact-minimal", "private": false, "owner": { "login": "aganglada", "id": 922348, "node_id": "MDQ6VXNlcjkyMjM0OA==", "avatar_url": "https://avatars0.githubusercontent.com/u/922348?v=4", "gravatar_id": "", "url": "https://api.github.com/users/aganglada", "html_url": "https://github.com/aganglada", "followers_url": "https://api.github.com/users/aganglada/followers", "following_url": "https://api.github.com/users/aganglada/following{/other_user}", "gists_url": "https://api.github.com/users/aganglada/gists{/gist_id}", "starred_url": "https://api.github.com/users/aganglada/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/aganglada/subscriptions", "organizations_url": "https://api.github.com/users/aganglada/orgs", "repos_url": "https://api.github.com/users/aganglada/repos", "events_url": "https://api.github.com/users/aganglada/events{/privacy}", "received_events_url": "https://api.github.com/users/aganglada/received_events", "type": "User", "site_admin": false }, "html_url": "https://github.com/aganglada/preact-minimal", "description": "🚀 Minimal preact structure", "fork": false, "url": "https://api.github.com/repos/aganglada/preact-minimal", "forks_url": "https://api.github.com/repos/aganglada/preact-minimal/forks", "keys_url": "https://api.github.com/repos/aganglada/preact-minimal/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/aganglada/preact-minimal/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/aganglada/preact-minimal/teams", "hooks_url": "https://api.github.com/repos/aganglada/preact-minimal/hooks", "issue_events_url": "https://api.github.com/repos/aganglada/preact-minimal/issues/events{/number}", "events_url": "https://api.github.com/repos/aganglada/preact-minimal/events", "assignees_url": "https://api.github.com/repos/aganglada/preact-minimal/assignees{/user}", "branches_url": "https://api.github.com/repos/aganglada/preact-minimal/branches{/branch}", "tags_url": "https://api.github.com/repos/aganglada/preact-minimal/tags", "blobs_url": "https://api.github.com/repos/aganglada/preact-minimal/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/aganglada/preact-minimal/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/aganglada/preact-minimal/git/refs{/sha}", "trees_url": "https://api.github.com/repos/aganglada/preact-minimal/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/aganglada/preact-minimal/statuses/{sha}", "languages_url": "https://api.github.com/repos/aganglada/preact-minimal/languages", "stargazers_url": "https://api.github.com/repos/aganglada/preact-minimal/stargazers", "contributors_url": "https://api.github.com/repos/aganglada/preact-minimal/contributors", "subscribers_url": "https://api.github.com/repos/aganglada/preact-minimal/subscribers", "subscription_url": "https://api.github.com/repos/aganglada/preact-minimal/subscription", "commits_url": "https://api.github.com/repos/aganglada/preact-minimal/commits{/sha}", "git_commits_url": "https://api.github.com/repos/aganglada/preact-minimal/git/commits{/sha}", "comments_url": "https://api.github.com/repos/aganglada/preact-minimal/comments{/number}", "issue_comment_url": "https://api.github.com/repos/aganglada/preact-minimal/issues/comments{/number}", "contents_url": "https://api.github.com/repos/aganglada/preact-minimal/contents/{+path}", "compare_url": "https://api.github.com/repos/aganglada/preact-minimal/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/aganglada/preact-minimal/merges", "archive_url": "https://api.github.com/repos/aganglada/preact-minimal/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/aganglada/preact-minimal/downloads", "issues_url": "https://api.github.com/repos/aganglada/preact-minimal/issues{/number}", "pulls_url": "https://api.github.com/repos/aganglada/preact-minimal/pulls{/number}", "milestones_url": "https://api.github.com/repos/aganglada/preact-minimal/milestones{/number}", "notifications_url": "https://api.github.com/repos/aganglada/preact-minimal/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/aganglada/preact-minimal/labels{/name}", "releases_url": "https://api.github.com/repos/aganglada/preact-minimal/releases{/id}", "deployments_url": "https://api.github.com/repos/aganglada/preact-minimal/deployments", "created_at": "2017-02-11T22:22:11Z", "updated_at": "2020-04-23T20:31:50Z", "pushed_at": "2019-09-19T14:22:23Z", "git_url": "git://github.com/aganglada/preact-minimal.git", "ssh_url": "git@github.com:aganglada/preact-minimal.git", "clone_url": "https://github.com/aganglada/preact-minimal.git", "svn_url": "https://github.com/aganglada/preact-minimal", "homepage": "", "size": 283, "stargazers_count": 132, "watchers_count": 132, "language": "JavaScript", "has_issues": true, "has_projects": true, "has_downloads": true, "has_wiki": true, "has_pages": false, "forks_count": 19, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 3, "license": { "key": "mit", "name": "MIT License", "spdx_id": "MIT", "url": "https://api.github.com/licenses/mit", "node_id": "MDc6TGljZW5zZTEz" }, "forks": 19, "open_issues": 3, "watchers": 132, "default_branch": "master", "score": 1.0 }, { "id": 50375662, "node_id": "MDEwOlJlcG9zaXRvcnk1MDM3NTY2Mg==", "name": "preact-markup", "full_name": "developit/preact-markup", "private": false, "owner": { "login": "developit", "id": 105127, "node_id": "MDQ6VXNlcjEwNTEyNw==", "avatar_url": "https://avatars2.githubusercontent.com/u/105127?v=4", "gravatar_id": "", "url": "https://api.github.com/users/developit", "html_url": "https://github.com/developit", "followers_url": "https://api.github.com/users/developit/followers", "following_url": "https://api.github.com/users/developit/following{/other_user}", "gists_url": "https://api.github.com/users/developit/gists{/gist_id}", "starred_url": "https://api.github.com/users/developit/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/developit/subscriptions", "organizations_url": "https://api.github.com/users/developit/orgs", "repos_url": "https://api.github.com/users/developit/repos", "events_url": "https://api.github.com/users/developit/events{/privacy}", "received_events_url": "https://api.github.com/users/developit/received_events", "type": "User", "site_admin": false }, "html_url": "https://github.com/developit/preact-markup", "description": ":zap: Render HTML5 as VDOM, with Components as Custom Elements!", "fork": false, "url": "https://api.github.com/repos/developit/preact-markup", "forks_url": "https://api.github.com/repos/developit/preact-markup/forks", "keys_url": "https://api.github.com/repos/developit/preact-markup/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/developit/preact-markup/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/developit/preact-markup/teams", "hooks_url": "https://api.github.com/repos/developit/preact-markup/hooks", "issue_events_url": "https://api.github.com/repos/developit/preact-markup/issues/events{/number}", "events_url": "https://api.github.com/repos/developit/preact-markup/events", "assignees_url": "https://api.github.com/repos/developit/preact-markup/assignees{/user}", "branches_url": "https://api.github.com/repos/developit/preact-markup/branches{/branch}", "tags_url": "https://api.github.com/repos/developit/preact-markup/tags", "blobs_url": "https://api.github.com/repos/developit/preact-markup/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/developit/preact-markup/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/developit/preact-markup/git/refs{/sha}", "trees_url": "https://api.github.com/repos/developit/preact-markup/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/developit/preact-markup/statuses/{sha}", "languages_url": "https://api.github.com/repos/developit/preact-markup/languages", "stargazers_url": "https://api.github.com/repos/developit/preact-markup/stargazers", "contributors_url": "https://api.github.com/repos/developit/preact-markup/contributors", "subscribers_url": "https://api.github.com/repos/developit/preact-markup/subscribers", "subscription_url": "https://api.github.com/repos/developit/preact-markup/subscription", "commits_url": "https://api.github.com/repos/developit/preact-markup/commits{/sha}", "git_commits_url": "https://api.github.com/repos/developit/preact-markup/git/commits{/sha}", "comments_url": "https://api.github.com/repos/developit/preact-markup/comments{/number}", "issue_comment_url": "https://api.github.com/repos/developit/preact-markup/issues/comments{/number}", "contents_url": "https://api.github.com/repos/developit/preact-markup/contents/{+path}", "compare_url": "https://api.github.com/repos/developit/preact-markup/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/developit/preact-markup/merges", "archive_url": "https://api.github.com/repos/developit/preact-markup/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/developit/preact-markup/downloads", "issues_url": "https://api.github.com/repos/developit/preact-markup/issues{/number}", "pulls_url": "https://api.github.com/repos/developit/preact-markup/pulls{/number}", "milestones_url": "https://api.github.com/repos/developit/preact-markup/milestones{/number}", "notifications_url": "https://api.github.com/repos/developit/preact-markup/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/developit/preact-markup/labels{/name}", "releases_url": "https://api.github.com/repos/developit/preact-markup/releases{/id}", "deployments_url": "https://api.github.com/repos/developit/preact-markup/deployments", "created_at": "2016-01-25T19:36:48Z", "updated_at": "2020-05-14T04:24:16Z", "pushed_at": "2020-04-22T22:17:43Z", "git_url": "git://github.com/developit/preact-markup.git", "ssh_url": "git@github.com:developit/preact-markup.git", "clone_url": "https://github.com/developit/preact-markup.git", "svn_url": "https://github.com/developit/preact-markup", "homepage": "http://npm.im/preact-markup", "size": 185, "stargazers_count": 143, "watchers_count": 143, "language": "JavaScript", "has_issues": true, "has_projects": true, "has_downloads": true, "has_wiki": false, "has_pages": false, "forks_count": 26, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 20, "license": { "key": "mit", "name": "MIT License", "spdx_id": "MIT", "url": "https://api.github.com/licenses/mit", "node_id": "MDc6TGljZW5zZTEz" }, "forks": 26, "open_issues": 20, "watchers": 143, "default_branch": "master", "score": 1.0 }, { "id": 47047829, "node_id": "MDEwOlJlcG9zaXRvcnk0NzA0NzgyOQ==", "name": "preact-todomvc", "full_name": "developit/preact-todomvc", "private": false, "owner": { "login": "developit", "id": 105127, "node_id": "MDQ6VXNlcjEwNTEyNw==", "avatar_url": "https://avatars2.githubusercontent.com/u/105127?v=4", "gravatar_id": "", "url": "https://api.github.com/users/developit", "html_url": "https://github.com/developit", "followers_url": "https://api.github.com/users/developit/followers", "following_url": "https://api.github.com/users/developit/following{/other_user}", "gists_url": "https://api.github.com/users/developit/gists{/gist_id}", "starred_url": "https://api.github.com/users/developit/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/developit/subscriptions", "organizations_url": "https://api.github.com/users/developit/orgs", "repos_url": "https://api.github.com/users/developit/repos", "events_url": "https://api.github.com/users/developit/events{/privacy}", "received_events_url": "https://api.github.com/users/developit/received_events", "type": "User", "site_admin": false }, "html_url": "https://github.com/developit/preact-todomvc", "description": "💣 TodoMVC done in Preact. Under 6kb and fast.", "fork": false, "url": "https://api.github.com/repos/developit/preact-todomvc", "forks_url": "https://api.github.com/repos/developit/preact-todomvc/forks", "keys_url": "https://api.github.com/repos/developit/preact-todomvc/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/developit/preact-todomvc/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/developit/preact-todomvc/teams", "hooks_url": "https://api.github.com/repos/developit/preact-todomvc/hooks", "issue_events_url": "https://api.github.com/repos/developit/preact-todomvc/issues/events{/number}", "events_url": "https://api.github.com/repos/developit/preact-todomvc/events", "assignees_url": "https://api.github.com/repos/developit/preact-todomvc/assignees{/user}", "branches_url": "https://api.github.com/repos/developit/preact-todomvc/branches{/branch}", "tags_url": "https://api.github.com/repos/developit/preact-todomvc/tags", "blobs_url": "https://api.github.com/repos/developit/preact-todomvc/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/developit/preact-todomvc/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/developit/preact-todomvc/git/refs{/sha}", "trees_url": "https://api.github.com/repos/developit/preact-todomvc/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/developit/preact-todomvc/statuses/{sha}", "languages_url": "https://api.github.com/repos/developit/preact-todomvc/languages", "stargazers_url": "https://api.github.com/repos/developit/preact-todomvc/stargazers", "contributors_url": "https://api.github.com/repos/developit/preact-todomvc/contributors", "subscribers_url": "https://api.github.com/repos/developit/preact-todomvc/subscribers", "subscription_url": "https://api.github.com/repos/developit/preact-todomvc/subscription", "commits_url": "https://api.github.com/repos/developit/preact-todomvc/commits{/sha}", "git_commits_url": "https://api.github.com/repos/developit/preact-todomvc/git/commits{/sha}", "comments_url": "https://api.github.com/repos/developit/preact-todomvc/comments{/number}", "issue_comment_url": "https://api.github.com/repos/developit/preact-todomvc/issues/comments{/number}", "contents_url": "https://api.github.com/repos/developit/preact-todomvc/contents/{+path}", "compare_url": "https://api.github.com/repos/developit/preact-todomvc/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/developit/preact-todomvc/merges", "archive_url": "https://api.github.com/repos/developit/preact-todomvc/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/developit/preact-todomvc/downloads", "issues_url": "https://api.github.com/repos/developit/preact-todomvc/issues{/number}", "pulls_url": "https://api.github.com/repos/developit/preact-todomvc/pulls{/number}", "milestones_url": "https://api.github.com/repos/developit/preact-todomvc/milestones{/number}", "notifications_url": "https://api.github.com/repos/developit/preact-todomvc/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/developit/preact-todomvc/labels{/name}", "releases_url": "https://api.github.com/repos/developit/preact-todomvc/releases{/id}", "deployments_url": "https://api.github.com/repos/developit/preact-todomvc/deployments", "created_at": "2015-11-29T03:03:19Z", "updated_at": "2020-05-07T14:14:28Z", "pushed_at": "2019-04-30T12:16:58Z", "git_url": "git://github.com/developit/preact-todomvc.git", "ssh_url": "git@github.com:developit/preact-todomvc.git", "clone_url": "https://github.com/developit/preact-todomvc.git", "svn_url": "https://github.com/developit/preact-todomvc", "homepage": "https://preact-todomvc.surge.sh", "size": 163, "stargazers_count": 79, "watchers_count": 79, "language": "JavaScript", "has_issues": true, "has_projects": true, "has_downloads": true, "has_wiki": false, "has_pages": true, "forks_count": 29, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 4, "license": null, "forks": 29, "open_issues": 4, "watchers": 79, "default_branch": "master", "score": 1.0 }, { "id": 68534861, "node_id": "MDEwOlJlcG9zaXRvcnk2ODUzNDg2MQ==", "name": "react-hint", "full_name": "slmgc/react-hint", "private": false, "owner": { "login": "slmgc", "id": 5236786, "node_id": "MDQ6VXNlcjUyMzY3ODY=", "avatar_url": "https://avatars1.githubusercontent.com/u/5236786?v=4", "gravatar_id": "", "url": "https://api.github.com/users/slmgc", "html_url": "https://github.com/slmgc", "followers_url": "https://api.github.com/users/slmgc/followers", "following_url": "https://api.github.com/users/slmgc/following{/other_user}", "gists_url": "https://api.github.com/users/slmgc/gists{/gist_id}", "starred_url": "https://api.github.com/users/slmgc/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/slmgc/subscriptions", "organizations_url": "https://api.github.com/users/slmgc/orgs", "repos_url": "https://api.github.com/users/slmgc/repos", "events_url": "https://api.github.com/users/slmgc/events{/privacy}", "received_events_url": "https://api.github.com/users/slmgc/received_events", "type": "User", "site_admin": false }, "html_url": "https://github.com/slmgc/react-hint", "description": "Tooltip component for React, Preact, Inferno", "fork": false, "url": "https://api.github.com/repos/slmgc/react-hint", "forks_url": "https://api.github.com/repos/slmgc/react-hint/forks", "keys_url": "https://api.github.com/repos/slmgc/react-hint/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/slmgc/react-hint/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/slmgc/react-hint/teams", "hooks_url": "https://api.github.com/repos/slmgc/react-hint/hooks", "issue_events_url": "https://api.github.com/repos/slmgc/react-hint/issues/events{/number}", "events_url": "https://api.github.com/repos/slmgc/react-hint/events", "assignees_url": "https://api.github.com/repos/slmgc/react-hint/assignees{/user}", "branches_url": "https://api.github.com/repos/slmgc/react-hint/branches{/branch}", "tags_url": "https://api.github.com/repos/slmgc/react-hint/tags", "blobs_url": "https://api.github.com/repos/slmgc/react-hint/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/slmgc/react-hint/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/slmgc/react-hint/git/refs{/sha}", "trees_url": "https://api.github.com/repos/slmgc/react-hint/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/slmgc/react-hint/statuses/{sha}", "languages_url": "https://api.github.com/repos/slmgc/react-hint/languages", "stargazers_url": "https://api.github.com/repos/slmgc/react-hint/stargazers", "contributors_url": "https://api.github.com/repos/slmgc/react-hint/contributors", "subscribers_url": "https://api.github.com/repos/slmgc/react-hint/subscribers", "subscription_url": "https://api.github.com/repos/slmgc/react-hint/subscription", "commits_url": "https://api.github.com/repos/slmgc/react-hint/commits{/sha}", "git_commits_url": "https://api.github.com/repos/slmgc/react-hint/git/commits{/sha}", "comments_url": "https://api.github.com/repos/slmgc/react-hint/comments{/number}", "issue_comment_url": "https://api.github.com/repos/slmgc/react-hint/issues/comments{/number}", "contents_url": "https://api.github.com/repos/slmgc/react-hint/contents/{+path}", "compare_url": "https://api.github.com/repos/slmgc/react-hint/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/slmgc/react-hint/merges", "archive_url": "https://api.github.com/repos/slmgc/react-hint/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/slmgc/react-hint/downloads", "issues_url": "https://api.github.com/repos/slmgc/react-hint/issues{/number}", "pulls_url": "https://api.github.com/repos/slmgc/react-hint/pulls{/number}", "milestones_url": "https://api.github.com/repos/slmgc/react-hint/milestones{/number}", "notifications_url": "https://api.github.com/repos/slmgc/react-hint/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/slmgc/react-hint/labels{/name}", "releases_url": "https://api.github.com/repos/slmgc/react-hint/releases{/id}", "deployments_url": "https://api.github.com/repos/slmgc/react-hint/deployments", "created_at": "2016-09-18T16:57:59Z", "updated_at": "2020-05-13T21:14:30Z", "pushed_at": "2020-04-14T02:55:58Z", "git_url": "git://github.com/slmgc/react-hint.git", "ssh_url": "git@github.com:slmgc/react-hint.git", "clone_url": "https://github.com/slmgc/react-hint.git", "svn_url": "https://github.com/slmgc/react-hint", "homepage": "https://react-hint.js.org/", "size": 215, "stargazers_count": 333, "watchers_count": 333, "language": "JavaScript", "has_issues": true, "has_projects": true, "has_downloads": true, "has_wiki": false, "has_pages": true, "forks_count": 27, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 10, "license": { "key": "mit", "name": "MIT License", "spdx_id": "MIT", "url": "https://api.github.com/licenses/mit", "node_id": "MDc6TGljZW5zZTEz" }, "forks": 27, "open_issues": 10, "watchers": 333, "default_branch": "master", "score": 1.0 }, { "id": 110067296, "node_id": "MDEwOlJlcG9zaXRvcnkxMTAwNjcyOTY=", "name": "preact-fluid", "full_name": "ajainvivek/preact-fluid", "private": false, "owner": { "login": "ajainvivek", "id": 1182600, "node_id": "MDQ6VXNlcjExODI2MDA=", "avatar_url": "https://avatars2.githubusercontent.com/u/1182600?v=4", "gravatar_id": "", "url": "https://api.github.com/users/ajainvivek", "html_url": "https://github.com/ajainvivek", "followers_url": "https://api.github.com/users/ajainvivek/followers", "following_url": "https://api.github.com/users/ajainvivek/following{/other_user}", "gists_url": "https://api.github.com/users/ajainvivek/gists{/gist_id}", "starred_url": "https://api.github.com/users/ajainvivek/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/ajainvivek/subscriptions", "organizations_url": "https://api.github.com/users/ajainvivek/orgs", "repos_url": "https://api.github.com/users/ajainvivek/repos", "events_url": "https://api.github.com/users/ajainvivek/events{/privacy}", "received_events_url": "https://api.github.com/users/ajainvivek/received_events", "type": "User", "site_admin": false }, "html_url": "https://github.com/ajainvivek/preact-fluid", "description": "✅ A minimal UI kit for Preact", "fork": false, "url": "https://api.github.com/repos/ajainvivek/preact-fluid", "forks_url": "https://api.github.com/repos/ajainvivek/preact-fluid/forks", "keys_url": "https://api.github.com/repos/ajainvivek/preact-fluid/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/ajainvivek/preact-fluid/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/ajainvivek/preact-fluid/teams", "hooks_url": "https://api.github.com/repos/ajainvivek/preact-fluid/hooks", "issue_events_url": "https://api.github.com/repos/ajainvivek/preact-fluid/issues/events{/number}", "events_url": "https://api.github.com/repos/ajainvivek/preact-fluid/events", "assignees_url": "https://api.github.com/repos/ajainvivek/preact-fluid/assignees{/user}", "branches_url": "https://api.github.com/repos/ajainvivek/preact-fluid/branches{/branch}", "tags_url": "https://api.github.com/repos/ajainvivek/preact-fluid/tags", "blobs_url": "https://api.github.com/repos/ajainvivek/preact-fluid/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/ajainvivek/preact-fluid/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/ajainvivek/preact-fluid/git/refs{/sha}", "trees_url": "https://api.github.com/repos/ajainvivek/preact-fluid/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/ajainvivek/preact-fluid/statuses/{sha}", "languages_url": "https://api.github.com/repos/ajainvivek/preact-fluid/languages", "stargazers_url": "https://api.github.com/repos/ajainvivek/preact-fluid/stargazers", "contributors_url": "https://api.github.com/repos/ajainvivek/preact-fluid/contributors", "subscribers_url": "https://api.github.com/repos/ajainvivek/preact-fluid/subscribers", "subscription_url": "https://api.github.com/repos/ajainvivek/preact-fluid/subscription", "commits_url": "https://api.github.com/repos/ajainvivek/preact-fluid/commits{/sha}", "git_commits_url": "https://api.github.com/repos/ajainvivek/preact-fluid/git/commits{/sha}", "comments_url": "https://api.github.com/repos/ajainvivek/preact-fluid/comments{/number}", "issue_comment_url": "https://api.github.com/repos/ajainvivek/preact-fluid/issues/comments{/number}", "contents_url": "https://api.github.com/repos/ajainvivek/preact-fluid/contents/{+path}", "compare_url": "https://api.github.com/repos/ajainvivek/preact-fluid/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/ajainvivek/preact-fluid/merges", "archive_url": "https://api.github.com/repos/ajainvivek/preact-fluid/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/ajainvivek/preact-fluid/downloads", "issues_url": "https://api.github.com/repos/ajainvivek/preact-fluid/issues{/number}", "pulls_url": "https://api.github.com/repos/ajainvivek/preact-fluid/pulls{/number}", "milestones_url": "https://api.github.com/repos/ajainvivek/preact-fluid/milestones{/number}", "notifications_url": "https://api.github.com/repos/ajainvivek/preact-fluid/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/ajainvivek/preact-fluid/labels{/name}", "releases_url": "https://api.github.com/repos/ajainvivek/preact-fluid/releases{/id}", "deployments_url": "https://api.github.com/repos/ajainvivek/preact-fluid/deployments", "created_at": "2017-11-09T04:29:37Z", "updated_at": "2020-05-14T17:36:11Z", "pushed_at": "2019-02-20T07:46:32Z", "git_url": "git://github.com/ajainvivek/preact-fluid.git", "ssh_url": "git@github.com:ajainvivek/preact-fluid.git", "clone_url": "https://github.com/ajainvivek/preact-fluid.git", "svn_url": "https://github.com/ajainvivek/preact-fluid", "homepage": "https://ajainvivek.github.io/preact-fluid/", "size": 921, "stargazers_count": 148, "watchers_count": 148, "language": "JavaScript", "has_issues": true, "has_projects": true, "has_downloads": true, "has_wiki": true, "has_pages": true, "forks_count": 23, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 5, "license": { "key": "mit", "name": "MIT License", "spdx_id": "MIT", "url": "https://api.github.com/licenses/mit", "node_id": "MDc6TGljZW5zZTEz" }, "forks": 23, "open_issues": 5, "watchers": 148, "default_branch": "master", "score": 1.0 }, { "id": 53229795, "node_id": "MDEwOlJlcG9zaXRvcnk1MzIyOTc5NQ==", "name": "preact-portal", "full_name": "developit/preact-portal", "private": false, "owner": { "login": "developit", "id": 105127, "node_id": "MDQ6VXNlcjEwNTEyNw==", "avatar_url": "https://avatars2.githubusercontent.com/u/105127?v=4", "gravatar_id": "", "url": "https://api.github.com/users/developit", "html_url": "https://github.com/developit", "followers_url": "https://api.github.com/users/developit/followers", "following_url": "https://api.github.com/users/developit/following{/other_user}", "gists_url": "https://api.github.com/users/developit/gists{/gist_id}", "starred_url": "https://api.github.com/users/developit/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/developit/subscriptions", "organizations_url": "https://api.github.com/users/developit/orgs", "repos_url": "https://api.github.com/users/developit/repos", "events_url": "https://api.github.com/users/developit/events{/privacy}", "received_events_url": "https://api.github.com/users/developit/received_events", "type": "User", "site_admin": false }, "html_url": "https://github.com/developit/preact-portal", "description": ":satellite: Render Preact components in (a) SPACE :milky_way: :stars:", "fork": false, "url": "https://api.github.com/repos/developit/preact-portal", "forks_url": "https://api.github.com/repos/developit/preact-portal/forks", "keys_url": "https://api.github.com/repos/developit/preact-portal/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/developit/preact-portal/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/developit/preact-portal/teams", "hooks_url": "https://api.github.com/repos/developit/preact-portal/hooks", "issue_events_url": "https://api.github.com/repos/developit/preact-portal/issues/events{/number}", "events_url": "https://api.github.com/repos/developit/preact-portal/events", "assignees_url": "https://api.github.com/repos/developit/preact-portal/assignees{/user}", "branches_url": "https://api.github.com/repos/developit/preact-portal/branches{/branch}", "tags_url": "https://api.github.com/repos/developit/preact-portal/tags", "blobs_url": "https://api.github.com/repos/developit/preact-portal/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/developit/preact-portal/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/developit/preact-portal/git/refs{/sha}", "trees_url": "https://api.github.com/repos/developit/preact-portal/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/developit/preact-portal/statuses/{sha}", "languages_url": "https://api.github.com/repos/developit/preact-portal/languages", "stargazers_url": "https://api.github.com/repos/developit/preact-portal/stargazers", "contributors_url": "https://api.github.com/repos/developit/preact-portal/contributors", "subscribers_url": "https://api.github.com/repos/developit/preact-portal/subscribers", "subscription_url": "https://api.github.com/repos/developit/preact-portal/subscription", "commits_url": "https://api.github.com/repos/developit/preact-portal/commits{/sha}", "git_commits_url": "https://api.github.com/repos/developit/preact-portal/git/commits{/sha}", "comments_url": "https://api.github.com/repos/developit/preact-portal/comments{/number}", "issue_comment_url": "https://api.github.com/repos/developit/preact-portal/issues/comments{/number}", "contents_url": "https://api.github.com/repos/developit/preact-portal/contents/{+path}", "compare_url": "https://api.github.com/repos/developit/preact-portal/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/developit/preact-portal/merges", "archive_url": "https://api.github.com/repos/developit/preact-portal/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/developit/preact-portal/downloads", "issues_url": "https://api.github.com/repos/developit/preact-portal/issues{/number}", "pulls_url": "https://api.github.com/repos/developit/preact-portal/pulls{/number}", "milestones_url": "https://api.github.com/repos/developit/preact-portal/milestones{/number}", "notifications_url": "https://api.github.com/repos/developit/preact-portal/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/developit/preact-portal/labels{/name}", "releases_url": "https://api.github.com/repos/developit/preact-portal/releases{/id}", "deployments_url": "https://api.github.com/repos/developit/preact-portal/deployments", "created_at": "2016-03-06T00:18:05Z", "updated_at": "2020-05-11T06:26:23Z", "pushed_at": "2019-09-15T00:27:49Z", "git_url": "git://github.com/developit/preact-portal.git", "ssh_url": "git@github.com:developit/preact-portal.git", "clone_url": "https://github.com/developit/preact-portal.git", "svn_url": "https://github.com/developit/preact-portal", "homepage": "http://npm.im/preact-portal", "size": 16, "stargazers_count": 152, "watchers_count": 152, "language": "JavaScript", "has_issues": true, "has_projects": true, "has_downloads": true, "has_wiki": true, "has_pages": false, "forks_count": 20, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 11, "license": { "key": "mit", "name": "MIT License", "spdx_id": "MIT", "url": "https://api.github.com/licenses/mit", "node_id": "MDc6TGljZW5zZTEz" }, "forks": 20, "open_issues": 11, "watchers": 152, "default_branch": "master", "score": 1.0 }, { "id": 56397188, "node_id": "MDEwOlJlcG9zaXRvcnk1NjM5NzE4OA==", "name": "preact-virtual-list", "full_name": "developit/preact-virtual-list", "private": false, "owner": { "login": "developit", "id": 105127, "node_id": "MDQ6VXNlcjEwNTEyNw==", "avatar_url": "https://avatars2.githubusercontent.com/u/105127?v=4", "gravatar_id": "", "url": "https://api.github.com/users/developit", "html_url": "https://github.com/developit", "followers_url": "https://api.github.com/users/developit/followers", "following_url": "https://api.github.com/users/developit/following{/other_user}", "gists_url": "https://api.github.com/users/developit/gists{/gist_id}", "starred_url": "https://api.github.com/users/developit/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/developit/subscriptions", "organizations_url": "https://api.github.com/users/developit/orgs", "repos_url": "https://api.github.com/users/developit/repos", "events_url": "https://api.github.com/users/developit/events{/privacy}", "received_events_url": "https://api.github.com/users/developit/received_events", "type": "User", "site_admin": false }, "html_url": "https://github.com/developit/preact-virtual-list", "description": ":card_index: Virtual List that only renders visible items. Supports millions of rows.", "fork": false, "url": "https://api.github.com/repos/developit/preact-virtual-list", "forks_url": "https://api.github.com/repos/developit/preact-virtual-list/forks", "keys_url": "https://api.github.com/repos/developit/preact-virtual-list/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/developit/preact-virtual-list/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/developit/preact-virtual-list/teams", "hooks_url": "https://api.github.com/repos/developit/preact-virtual-list/hooks", "issue_events_url": "https://api.github.com/repos/developit/preact-virtual-list/issues/events{/number}", "events_url": "https://api.github.com/repos/developit/preact-virtual-list/events", "assignees_url": "https://api.github.com/repos/developit/preact-virtual-list/assignees{/user}", "branches_url": "https://api.github.com/repos/developit/preact-virtual-list/branches{/branch}", "tags_url": "https://api.github.com/repos/developit/preact-virtual-list/tags", "blobs_url": "https://api.github.com/repos/developit/preact-virtual-list/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/developit/preact-virtual-list/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/developit/preact-virtual-list/git/refs{/sha}", "trees_url": "https://api.github.com/repos/developit/preact-virtual-list/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/developit/preact-virtual-list/statuses/{sha}", "languages_url": "https://api.github.com/repos/developit/preact-virtual-list/languages", "stargazers_url": "https://api.github.com/repos/developit/preact-virtual-list/stargazers", "contributors_url": "https://api.github.com/repos/developit/preact-virtual-list/contributors", "subscribers_url": "https://api.github.com/repos/developit/preact-virtual-list/subscribers", "subscription_url": "https://api.github.com/repos/developit/preact-virtual-list/subscription", "commits_url": "https://api.github.com/repos/developit/preact-virtual-list/commits{/sha}", "git_commits_url": "https://api.github.com/repos/developit/preact-virtual-list/git/commits{/sha}", "comments_url": "https://api.github.com/repos/developit/preact-virtual-list/comments{/number}", "issue_comment_url": "https://api.github.com/repos/developit/preact-virtual-list/issues/comments{/number}", "contents_url": "https://api.github.com/repos/developit/preact-virtual-list/contents/{+path}", "compare_url": "https://api.github.com/repos/developit/preact-virtual-list/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/developit/preact-virtual-list/merges", "archive_url": "https://api.github.com/repos/developit/preact-virtual-list/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/developit/preact-virtual-list/downloads", "issues_url": "https://api.github.com/repos/developit/preact-virtual-list/issues{/number}", "pulls_url": "https://api.github.com/repos/developit/preact-virtual-list/pulls{/number}", "milestones_url": "https://api.github.com/repos/developit/preact-virtual-list/milestones{/number}", "notifications_url": "https://api.github.com/repos/developit/preact-virtual-list/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/developit/preact-virtual-list/labels{/name}", "releases_url": "https://api.github.com/repos/developit/preact-virtual-list/releases{/id}", "deployments_url": "https://api.github.com/repos/developit/preact-virtual-list/deployments", "created_at": "2016-04-16T17:40:06Z", "updated_at": "2020-04-28T00:37:36Z", "pushed_at": "2019-07-22T23:14:11Z", "git_url": "git://github.com/developit/preact-virtual-list.git", "ssh_url": "git@github.com:developit/preact-virtual-list.git", "clone_url": "https://github.com/developit/preact-virtual-list.git", "svn_url": "https://github.com/developit/preact-virtual-list", "homepage": "https://jsfiddle.net/developit/qqan9pdo/", "size": 12, "stargazers_count": 184, "watchers_count": 184, "language": "JavaScript", "has_issues": true, "has_projects": true, "has_downloads": true, "has_wiki": true, "has_pages": false, "forks_count": 25, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 7, "license": { "key": "mit", "name": "MIT License", "spdx_id": "MIT", "url": "https://api.github.com/licenses/mit", "node_id": "MDc6TGljZW5zZTEz" }, "forks": 25, "open_issues": 7, "watchers": 184, "default_branch": "master", "score": 1.0 } ] } ================================================ FILE: example_old/fakeApi.js ================================================ export function fetchProfileData() { const userPromise = fetchUser(); const postsPromise = fetchPosts(); const triviaPromise = fetchTrivia(); return { user: wrapPromise(userPromise), posts: wrapPromise(postsPromise), trivia: wrapPromise(triviaPromise), }; } // Suspense integrations like Relay implement // a contract like this to integrate with React. // Real implementations can be significantly more complex. // Don't copy-paste this into your project! function wrapPromise(promise) { const promiseId = performance.now(); let status = 'pending'; let result; const suspender = promise.then( (r) => { // console.log('promiseId on success ---', promiseId, r); status = 'success'; result = r; return result; }, (e) => { status = 'error'; result = e; }, ); return { read() { if (status === 'pending') { // console.log('promiseId on suspend ---', promiseId); throw suspender; } else if (status === 'error') { throw result; } else if (status === 'success') { return result; } }, }; } function fetchUser() { // console.log('fetch user...'); return new Promise((resolve) => { setTimeout(() => { console.log('fetched user'); resolve({ name: 'Ringo Starr', }); }, 1000); }); } const ringoPosts = [ { id: 0, text: 'I get by with a little help from my friends', }, { id: 1, text: "I'd like to be under the sea in an octupus's garden", }, { id: 2, text: 'You got that sand all over your feet', }, ]; function fetchPosts() { const ringoPostsAtTheTime = ringoPosts; // console.log('fetch posts...'); return new Promise((resolve) => { setTimeout(() => { console.log('fetched posts'); resolve(ringoPostsAtTheTime); }, 2000); }); } function fetchTrivia() { return new Promise((resolve) => { setTimeout(() => { console.log('fetched trivia'); resolve([ { id: 1, text: 'The nickname "Ringo" came from his habit of wearing numerous rings.', }, { id: 2, text: 'Plays the drums left-handed with a right-handed drum set.', }, { id: 3, text: 'Nominated for one Daytime Emmy Award, but did not win', }, { id: 4, text: 'Nominated for one Daytime Emmy Award, and did win this time', }, ]); }, 3000); }); } ================================================ FILE: example_old/fakeApiSuspenseList.js ================================================ export function fetchProfileData() { const userPromise = fetchUser(); const postsPromise = fetchPosts(); const triviaPromise = fetchTrivia(); return { user: wrapPromise(userPromise), posts: wrapPromise(postsPromise), trivia: wrapPromise(triviaPromise), }; } // Suspense integrations like Relay implement // a contract like this to integrate with React. // Real implementations can be significantly more complex. // Don't copy-paste this into your project! function wrapPromise(promise) { let status = 'pending'; let result; const suspender = promise.then( (r) => { status = 'success'; result = r; }, (e) => { status = 'error'; result = e; }, ); return { read() { if (status === 'pending') { throw suspender; } else if (status === 'error') { throw result; } else if (status === 'success') { return result; } }, }; } function fetchUser() { console.log('fetch user...'); return new Promise((resolve) => { setTimeout(() => { console.log('fetched user'); resolve({ name: 'Ringo Starr', }); }, 500); }); } const ringoPosts = [ { id: 0, text: 'I get by with a little help from my friends', }, { id: 1, text: "I'd like to be under the sea in an octupus's garden", }, { id: 2, text: 'You got that sand all over your feet', }, ]; function fetchPosts() { const ringoPostsAtTheTime = ringoPosts; console.log('fetch posts...'); return new Promise((resolve) => { setTimeout(() => { console.log('fetched posts'); resolve(ringoPostsAtTheTime); }, 3000); }); } function fetchTrivia() { return new Promise((resolve) => { setTimeout(() => { console.log('fetched trivia'); resolve([ { id: 1, text: 'The nickname "Ringo" came from his habit of wearing numerous rings.', }, { id: 2, text: 'Plays the drums left-handed with a right-handed drum set.', }, { id: 3, text: 'Nominated for one Daytime Emmy Award, but did not win', }, ]); }, 4000); }); } ================================================ FILE: example_old/friends.json ================================================ [ { "_id": "5d029d30bc3e8f972d3aa50f", "name": "Roberta Wall" }, { "_id": "5d029d3022013fcdad4791d7", "name": "Ayala Bowers" }, { "_id": "5d029d30c27654147e7b103c", "name": "Frances Deleon" }, { "_id": "5d029d30a0087a9cb5c0f72e", "name": "Nell Summers" }, { "_id": "5d029d3046f20fff8ad9c81e", "name": "Glenda Garza" }, { "_id": "5d029d304cba24d1a145fc8c", "name": "Jimenez Mcgowan" }, { "_id": "5d029d30e2d4ac6ce472b5e8", "name": "Jessica Langley" }, { "_id": "5d029d306ad4ded1c5c951b7", "name": "Perez Mcknight" }, { "_id": "5d029d3017df1f3ed37e5f3a", "name": "Jeanne Moss" }, { "_id": "5d029d30b1b76e96f5969739", "name": "Carla Boyer" }, { "_id": "5d029d3044d6388bb864446c", "name": "Wendy Nash" } ] ================================================ FILE: example_old/index.html ================================================ Brahmos Demo

    App

    ================================================ FILE: example_old/index.js ================================================ import App from './App.js'; import UnMountAtNode from './UnMountAtNode'; import Brahmos, { render } from '../src'; import ConcurrentApp from './concurrentApp'; import SuspenseApp from './suspenseExamples'; import SuspenseListApp from './suspenseListExample'; render(, document.getElementById('app')); render(, document.getElementById('unmount-node')); ================================================ FILE: example_old/lazySuspenseExample.js ================================================ import Brahmos, { Suspense, lazy } from '../src'; import TodoList from './TodoList'; const LazyToDo = lazy(() => { return new Promise((resolve) => { setTimeout(() => { resolve(import('./TodoList')); }, 1000); }); }); const LazyUseStateExample = lazy(() => import('./UseStateExample')); export default function LazySuspenseExample() { const message = 'Hello world'; return ( LOADING !!!}>

    Hurray !!

    {message}

    hey !!

    {/** Adding TODOList to check if the lifecycle methods are called */}

    Something

    ); } ================================================ FILE: example_old/suspenseExamples.js ================================================ import Brahmos, { useState, useTransition, Suspense } from '../src'; import { fetchProfileData } from './fakeApi'; // const initialResource = fetchProfileData(0); export default function App() { const [tab, setTab] = useState('home'); function showProfile(id) { setTab('profile'); } let page; if (tab === 'home') { page = ; } else if (tab === 'profile') { page = ; } // return page; return Loading the app...}>{page}; } function HomePage({ showProfile }) { return ( <>

    Home Page

    ); } function Separator() { return
    Something Separator
    ; } function ProfilePage() { const [resource, setResource] = useState(); function showProfile(id) { setResource(fetchProfileData(id)); } return ( <> Loading posts...}> {resource && ( <> )} Loading timeline...}> {resource && ( <> Loading Trivia...}> )} {/* Loading timeline...}> {resource && ( <> Loading Trivia...}> )} */} ); } function ProfileDetails({ resource }) { const user = resource.user.read(); return

    {user.name}

    ; } function ProfileTimeline({ resource }) { const posts = resource.posts.read(); return (
      {posts.map((post) => (
    • {post.text}
    • ))}
    ); } function ProfileTrivia({ resource }) { const trivia = resource.trivia.read(); return ( <>

    Fun Facts

      {trivia.map((fact) => (
    • {fact.text}
    • ))}
    ); } function Button({ children, onClick }) { const [startTransition, isPending] = useTransition({ timeoutMs: 10000, }); function handleClick() { onClick(); // startTransition(() => { // onClick(); // }); } const spinner = ( Loading... ); return ( <> {isPending ? spinner : null} ); } ================================================ FILE: example_old/suspenseListExample.js ================================================ import Brahmos, { SuspenseList, Suspense } from '../src'; import { fetchProfileData } from './fakeApiSuspenseList'; const initialResource = fetchProfileData(0); export default function App() { return ; } function ProfilePage({ resource }) { return ( Loading...}> Loading posts...}> Loading fun facts...}> ); } function ProfileDetails({ resource }) { const user = resource.user.read(); return

    {user.name}

    ; } function ProfileTimeline({ resource }) { const posts = resource.posts.read(); return (
      {posts.map((post) => (
    • {post.text}
    • ))}
    ); } function ProfileTrivia({ resource }) { const trivia = resource.trivia.read(); return ( <>

    Fun Facts

      {trivia.map((fact) => (
    • {fact.text}
    • ))}
    ); } ================================================ FILE: jest.config.js ================================================ module.exports = { rootDir: './src', transform: { '^.+\\.js$': 'babel-jest', }, moduleNameMapper: { '^brahmos$': '/index.js', }, setupFilesAfterEnv: ['/__tests__/jest.setup.js'], testPathIgnorePatterns: ['jest.setup.js', 'testUtils.js'], }; ================================================ FILE: package.json ================================================ { "name": "brahmos", "version": "0.11.1", "description": "Super charged UI library with modern React API and native templates.", "main": "dist/brahmos.js", "module": "dist/brahmos.es.js", "author": "s-yadav ", "license": "MIT", "scripts": { "flow": "flow", "test": "jest", "test:watch": "jest --watch --collect-coverage=false", "test:debug": "node --inspect-brk ./node_modules/.bin/jest --runInBand --watch --collect-coverage=false", "test:bundlesize": "yarn bundle && bundlesize", "start": "webpack-dev-server --open", "build": "babel src --out-dir lib", "bundle": "cross-env NODE_ENV=production rollup -c rollup.config.js", "lint": "eslint ./src ./example --fix" }, "repository": { "type": "git", "url": "https://github.com/s-yadav/brahmos" }, "bugs": { "mail": "sudhanshuyadav2@gmail.com", "url": "https://github.com/s-yadav/brahmos/issues" }, "devDependencies": { "@ampproject/rollup-plugin-closure-compiler": "^0.26.0", "@babel/cli": "^7.11.6", "@babel/core": "^7.11.6", "@babel/plugin-proposal-class-properties": "^7.10.4", "@babel/plugin-proposal-object-rest-spread": "^7.11.0", "@babel/plugin-transform-flow-strip-types": "^7.10.4", "@babel/plugin-transform-runtime": "^7.11.5", "@babel/preset-env": "^7.11.5", "@babel/runtime": "^7.11.2", "@rollup/plugin-buble": "^0.21.3", "@rollup/plugin-commonjs": "^15.0.0", "@rollup/plugin-json": "^4.1.0", "@rollup/plugin-node-resolve": "^9.0.0", "@rollup/plugin-replace": "^2.3.3", "@testing-library/dom": "^7.26.6", "babel-eslint": "^10.1.0", "babel-jest": "^26.3.0", "babel-loader": "^8.1.0", "babel-plugin-brahmos": "0.6.1", "bulma": "^0.9.0", "bundlesize": "^0.18.0", "cross-env": "^7.0.2", "css-loader": "^4.3.0", "eslint": "^7.9.0", "eslint-config-prettier": "^6.11.0", "eslint-config-standard": "^14.1.1", "eslint-plugin-flowtype": "^5.2.0", "eslint-plugin-import": "^2.22.0", "eslint-plugin-jest": "^24.0.2", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^4.2.1", "eslint-plugin-standard": "^4.0.1", "flow-bin": "^0.133.0", "google-closure-compiler": "^20200830.0.0", "html-webpack-plugin": "^4.4.1", "jest": "^26.4.2", "js-beautify": "^1.13.0", "mobx": "^6.0.4", "mobx-react-lite": "^3.1.6", "prettier": "^2.1.2", "react-query": "^2.26.3", "react-redux": "^7.2.2", "react-router-dom": "^5.2.0", "recharts": "^1.8.5", "redux": "^4.0.5", "rollup": "^2.27.1", "rollup-plugin-filesize": "^9.0.2", "rollup-plugin-flow": "^1.1.1", "rollup-plugin-license": "^2.2.0", "rollup-plugin-terser": "^7.0.2", "sass": "^1.26.11", "sass-loader": "^10.0.2", "style-loader": "^1.2.1", "webpack": "^4.44.2", "webpack-cli": "^3.3.12", "webpack-dev-server": "^3.11.0", "zustand": "^3.2.0" }, "resolutions": { "@ampproject/rollup-plugin-closure-compiler/google-closure-compiler": "^20200830.0.0" } } ================================================ FILE: rollup.config.js ================================================ import fileSize from 'rollup-plugin-filesize'; import commonjs from '@rollup/plugin-commonjs'; import license from 'rollup-plugin-license'; import replace from '@rollup/plugin-replace'; import resolve from '@rollup/plugin-node-resolve'; import buble from '@rollup/plugin-buble'; import flow from 'rollup-plugin-flow'; import compiler from '@ampproject/rollup-plugin-closure-compiler'; import PACKAGE from './package.json'; const fullYear = new Date().getFullYear(); const banner = `${PACKAGE.name} - ${PACKAGE.version} Author : ${PACKAGE.author} Copyright (c) ${fullYear !== 2016 ? '2016,' : ''} ${fullYear} to ${ PACKAGE.author }, released under the ${PACKAGE.license} license. ${PACKAGE.repository.url}`; const rollupConfig = { input: './src/index.js', output: [ { file: 'dist/brahmos.es.js', format: 'esm', }, { file: 'dist/brahmos.js', format: 'umd', name: 'Brahmos', }, ], plugins: [ flow(), buble({ objectAssign: true, }), replace({ 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), }), resolve(), commonjs({ include: 'node_modules/**', }), fileSize(), license({ banner, }), compiler({ compilationLevel: 'SIMPLE', }), ], }; export default [rollupConfig]; ================================================ FILE: src/Children.js ================================================ // @flow import { BRAHMOS_DATA_KEY } from './configs'; import { isTagElementNode, isComponentNode, isTagNode, isHtmlTagNode } from './brahmosNode'; import { getNormalizedProps, isNil, toArray } from './utils'; import type { BrahmosNode, ArrayCallback, ObjectLiteral } from './flow.types'; import parseChildren from './parseChildren'; function isPlaceholderTagNode(children: BrahmosNode): boolean { /** * if the template string for a tag node does not have any thing, * it means the tag node is just used to wrap children. * Note: We always wrap the children except when children is already an array * or there is single child. We do this to make the all of the child positional * */ // $FlowFixMe: We are checking for tag node so template key will be present return isTagNode(children) && !children.template.strings.some(Boolean); } function flattenChildren(children) { let _children = []; children.forEach((child) => { if (Array.isArray(child)) { _children = _children.concat(flattenChildren(child)); } else if (child && isHtmlTagNode(child)) { _children.push(parseChildren(child)); } else { _children.push(child); } }); return _children; } function getChildrenArray(children: any): ?Array { if (isNil(children)) return undefined; else if (typeof children === 'boolean') return []; // if the transformed value is in cache return from the cache if (children[BRAHMOS_DATA_KEY]) return children[BRAHMOS_DATA_KEY]; let _children = children; if (isPlaceholderTagNode(_children)) { // if children is a tag node and is just a placeholder for all the children use the values from the node _children = _children.values; } // if the children is a html tag node parse the whole static tree if (isHtmlTagNode(_children)) { _children = parseChildren(_children); } if (!Array.isArray(_children)) _children = [_children]; // flatten the children _children = flattenChildren(_children); // store the transformed children in cache, so we don't perform same action again // Perf of adding random property in an array: https://www.measurethat.net/Benchmarks/Show/10737/1/adding-random-property-in-array children[BRAHMOS_DATA_KEY] = _children; return _children; } function map(children: any, cb: ArrayCallback): ?Array { const _children = getChildrenArray(children); if (!_children) return children; return _children.map(cb); } function childrenToArray(children: any): Array { const _children = getChildrenArray(children) || []; return _children.map((child, index) => { /** * As we have converted children to a flat array node * assign key to each elements if it isn't already present */ if (child && child.key === undefined) { child.key = index; } return child; }); } function forEach(children: any, cb: ArrayCallback): void { const _children = getChildrenArray(children) || []; _children.forEach(cb); } function only(children: any) { const _children = getChildrenArray(children); return _children && children.length === 1; } function count(children: any): number { const _children = getChildrenArray(children); return _children ? _children.length : 0; } export const Children = { map, toArray: childrenToArray, forEach, only, count, }; export function isValidElement(node: any) { return node && (isComponentNode(node) || isTagElementNode(node)); } export function cloneElement(node: any, props: ObjectLiteral): ?BrahmosNode { // extend props can be undefined, so have default value for it props = props || {}; const argLn = arguments.length; // We have to add children prop in case only when third param is provided. if (argLn > 2) { const children = argLn > 3 ? toArray(arguments, 2) : arguments[2]; props.children = children; } if (node) { if (isHtmlTagNode(node)) { const parsedChildren = parseChildren(node); return cloneElement(parsedChildren, props); } else if (isComponentNode(node) || isTagElementNode(node)) { return { ...node, props: { ...node.props, ...getNormalizedProps(props, false) }, ref: props.ref, }; } } return node; } ================================================ FILE: src/Component.js ================================================ // @flow import reRender from './reRender'; import { guardedSetState } from './updateUtils'; import { BRAHMOS_DATA_KEY } from './configs'; import type { ComponentInstance, PureComponentInstance, NewState, StateCallback, ObjectLiteral, } from './flow.types'; export class Component implements ComponentInstance { $key: any; $value: any; props: ObjectLiteral; state: ?ObjectLiteral; context: any; isReactComponent: boolean; constructor(props: ObjectLiteral) { this.props = props; this.state = undefined; this.context = undefined; this[BRAHMOS_DATA_KEY] = { lastSnapshot: null, pendingSyncUpdates: [], pendingDeferredUpdates: [], fiber: null, nodes: null, mounted: false, committedValues: {}, memoizedValues: null, isDirty: false, renderCount: 0, }; } setState(newState: NewState, callback: StateCallback) { const shouldRerender = guardedSetState(this, (transitionId) => ({ state: newState, transitionId, callback, })); if (shouldRerender) reRender(this); } forceUpdate(callback: StateCallback) { const brahmosData = this[BRAHMOS_DATA_KEY]; // if there is no fiber (when component is not mounted) we don't need to do anything const { fiber } = brahmosData; if (!fiber) return; // keep the track of component through which force update is started fiber.root.forcedUpdateWith = this; this[BRAHMOS_DATA_KEY].isDirty = true; reRender(this); if (callback) callback(this.state); } render() {} __render() { // get the new rendered node const nodes = this.render(); // store the current reference of nodes so we can use this this on next render cycle this[BRAHMOS_DATA_KEY].nodes = nodes; return nodes; } } Component.prototype.isReactComponent = true; export class PureComponent extends Component implements PureComponentInstance { isPureReactComponent: boolean; } PureComponent.prototype.isPureReactComponent = true; ================================================ FILE: src/Suspense.js ================================================ // @flow import { createBrahmosNode, Component } from './circularDep'; import { forwardRef } from './refs'; import { getPromiseSuspendedValue, timestamp, resolvedPromise, isMounted } from './utils'; import { PREDEFINED_TRANSITION_DEFERRED, getTransitionFromFiber, isTransitionCompleted, } from './transitionUtils'; import { withTransition, deferredUpdates } from './updateUtils'; import reRender from './reRender'; import { BRAHMOS_DATA_KEY, UPDATE_TYPE_DEFERRED, SUSPENSE_REVEAL_INTERVAL, TRANSITION_STATE_SUSPENDED, TRANSITION_STATE_TIMED_OUT, TRANSITION_STATE_RESOLVED, TRANSITION_STATE_START, } from './configs'; import { getCurrentComponentFiber, getFiberFromComponent, setUpdateTime } from './fiber'; import type { Fiber, Transition, AnyTransition, SuspenseProps, SuspenseListProps, SuspenseInstance, SuspenseListInstance, ClassComponent, FunctionalComponent, } from './flow.types'; type LazyComponentModule = { default: ClassComponent | FunctionalComponent, }; export function getClosestSuspenseFiber(fiber: Fiber, includeSuspenseList: boolean): ?Fiber { const { root } = fiber; let { nodeInstance } = fiber; while ( !( nodeInstance instanceof Suspense || /* eslint-disable no-unmodified-loop-condition */ (includeSuspenseList && nodeInstance instanceof SuspenseList) ) ) { fiber = fiber.parent; if (fiber === root) return null; nodeInstance = fiber.nodeInstance; } return fiber; } export function resetSiblingFibers(fiber: Fiber): Fiber { const parentSuspenseFiber = getClosestSuspenseFiber(fiber.parent, true); const isSuspenseList = parentSuspenseFiber && parentSuspenseFiber.nodeInstance instanceof SuspenseList; // if parent is not a suspense list we don't have to do anything if (!isSuspenseList) return fiber; // $FlowFixMe: This will not come here if parentSuspenseFiber is not present. const { nodeInstance: component } = parentSuspenseFiber; const { childManagers } = component.suspenseManagers[ getTransitionFromFiber(fiber, null).transitionId ]; const { revealOrder } = component.props; /** * If a Suspense is suspended in case of backwards and together we should reset all * siblings as dirty as they might need processing again. * For forwards revealOrder we don't have to do anything as we in work loop we loop * in forward direction only */ if (revealOrder === 'backwards' || revealOrder === 'together') { childManagers.forEach((manager) => { manager.component[BRAHMOS_DATA_KEY].isDirty = true; }); return childManagers[0].fiber; } return fiber; } function getClosestSuspenseListManager(manager) { const { parentSuspenseManager } = manager; return parentSuspenseManager && parentSuspenseManager.isSuspenseList ? parentSuspenseManager : null; } // eslint-disable-next-line no-use-before-define function getSuspenseManager(fiber: Fiber, transition: AnyTransition): SuspenseManager { const { nodeInstance: component } = fiber; const { suspenseManagers } = component; const { transitionId } = transition; let suspenseManager = suspenseManagers[transitionId]; if (!suspenseManager) { suspenseManager = suspenseManagers[transitionId] = new SuspenseManager(fiber, transition); } // if the transition is on start state (restarted), remove the earlier suspender if (transition.transitionState === TRANSITION_STATE_START) { suspenseManager.suspender = null; } return suspenseManager; } function markComponentDirty(component: SuspenseInstance | SuspenseListInstance) { component[BRAHMOS_DATA_KEY].isDirty = true; } function markManagerDirty(manager) { const fiber = getFiberFromComponent(manager.component); if (manager.isUnresolved() && fiber) { markComponentDirty(manager.component); setUpdateTime(fiber, UPDATE_TYPE_DEFERRED); } } class SuspenseManager { fiber: Fiber; component: SuspenseInstance | SuspenseListInstance; transition: AnyTransition; childManagers: Array; suspender: ?Promise; isSuspenseList: boolean; parentSuspenseManager: ?SuspenseManager; rootSuspenseManager: SuspenseManager; handleSuspense: () => Promise; constructor(fiber, transition) { const { nodeInstance } = fiber; this.fiber = fiber; // this is just for reference for suspense which gets resolved before committed this.component = nodeInstance; this.transition = transition; this.childManagers = []; this.suspender = null; this.isSuspenseList = nodeInstance instanceof SuspenseList; const parentSuspenseFiber = getClosestSuspenseFiber(fiber.parent, true); this.parentSuspenseManager = parentSuspenseFiber && getSuspenseManager(parentSuspenseFiber, transition); this.recordChildSuspense(); // bind handleSuspense this.handleSuspense = this.handleSuspense.bind(this); } recordChildSuspense() { const { parentSuspenseManager } = this; if (parentSuspenseManager) { parentSuspenseManager.childManagers.push(this); this.rootSuspenseManager = parentSuspenseManager.rootSuspenseManager; } else { this.rootSuspenseManager = this; } } addRootToProcess() { const { rootSuspenseManager } = this; const { root } = getCurrentComponentFiber(); root.afterRender(rootSuspenseManager.handleSuspense); } suspend(suspender) { this.suspender = suspender; // mark root suspense to process this.addRootToProcess(); } handleSuspense() { const { component, suspender } = this; const isSuspenseList = component instanceof SuspenseList; if (isSuspenseList) { return this.handleSuspenseList(); } return Promise.resolve(suspender).then(this.resolve.bind(this, suspender)); } isUnresolved() { // if the manager is a suspense list, check if any of the child is unresolved // or in case of suspense if has a suspender it means its unresolved. if (this.isSuspenseList) { return this.childManagers.some((manager) => manager.isUnresolved()); } else { return this.suspender; } } shouldShowFallback() { const suspenseListManager = getClosestSuspenseListManager(this); // if there is no closest suspense list manager then return true if (!suspenseListManager) return true; const { component: suspenseList, childManagers: siblingManagers } = suspenseListManager; // get the parent of suspenseListManger // $FlowFixMe: It comes here only for suspense list, so can be ignored const { tail } = suspenseList.props; // get the parent of suspenseListManger const parentSuspenseListManager = getClosestSuspenseListManager(suspenseListManager); /** * If suspense list has another parent suspense list, and the suspense list * is marked to not show fallback return false * * Or else in case of collapsed show the fallback on the first unresolved suspense. */ if (parentSuspenseListManager && !suspenseListManager.shouldShowFallback()) { return false; } else if (tail === 'collapsed') { for (let i = 0, ln = siblingManagers.length; i < ln; i++) { const manager = siblingManagers[i]; /** * If any of previous manager is suspended and which is not same as the manager * we are testing then return false */ if (tail === 'collapsed' && manager.isUnresolved()) { return manager === this; } } } return tail !== 'hidden'; } shouldRenderChildren() { const suspenseListManager = getClosestSuspenseListManager(this); const { suspender } = this; // if there is no closest suspense list manager then return based on it has suspender if (!suspenseListManager) return !suspender; /** * Also, if component is rendered without suspend once, we should not bring it * to suspended state */ if (isMounted(this.component) && !suspender) return true; /** * if parent suspenseList has a reveal order and sibling (based on reveal order) * is not resolved yet, we need to wait for the sibling to resolve */ const { component: { // $FlowFixMe: It comes here only for suspense list, so can be ignored props: { revealOrder }, }, childManagers, } = suspenseListManager; const suspenseIndex = childManagers.indexOf(this); const hasSuspendedSibling = childManagers.some((manager, index) => { const { suspender } = manager; if (suspender) { return ( revealOrder === 'together' || (revealOrder === 'forwards' && index <= suspenseIndex) || (revealOrder === 'backwards' && index >= suspenseIndex) ); } return false; }); return !hasSuspendedSibling; } resolve(resolvedWithSuspender) { const { component, transition, suspender, childManagers } = this; const pendingSuspense = transition.pendingSuspense || []; /** * if it is resolved with different suspender then current suspender * Then no nee to process further */ if (resolvedWithSuspender !== suspender) return; /** * if there isn't any suspender, child managers may have suspender * Loop on child manager and handle their suspense */ if (!suspender) { childManagers.forEach((manager) => { manager.handleSuspense(); }); return; } // mark the suspense to be resolved and component as dirty this.suspender = null; markComponentDirty(this.component); const transitionTimedOut = transition.transitionState === TRANSITION_STATE_TIMED_OUT; // Get the unresolved suspense for the transition. const transitionHasUnresolvedSuspense = pendingSuspense.filter( (suspense) => suspense.suspenseManagers[transition.transitionId].suspender, ).length; /** * set transition state as resolved if transition is not timed out and it doesn't * have any unresolved sibling */ if (!transitionTimedOut && !transitionHasUnresolvedSuspense) { // $FlowFixMe: We have check for not timed out, so error can be ignored transition.transitionState = TRANSITION_STATE_RESOLVED; } /** * If the transition is timed out or the suspense is not part of * the transition pendingSuspense list we need to do normal deferred rendering * Otherwise do re-render with the transition. */ const doSuspenseRerender = () => { let targetComponent = component; /** * If there is no fiber reference on the component, it means suspense is resolved before commit. * In which case there must be some parent which has pending update. * So we just need to restart deferred workLoop, which we can do by rerendering from wip fiber. */ // $FlowFixMe: wip node will always be present after first render if (!getFiberFromComponent(component)) targetComponent = this.fiber.root.wip.nodeInstance; reRender(targetComponent); }; // trigger rerender on specific intervals setTimeout(() => { if (transitionTimedOut || !pendingSuspense.includes(component)) { deferredUpdates(doSuspenseRerender); } else { withTransition(transition, doSuspenseRerender); } }, timestamp() % SUSPENSE_REVEAL_INTERVAL); } getChildrenSuspenders() { let childSuspenders = []; this.childManagers.forEach((manager) => { if (manager.isSuspenseList) { childSuspenders = childSuspenders.concat(manager.getChildrenSuspenders()); } else if (manager.suspender) { childSuspenders.push(manager.suspender); } }); return childSuspenders; } handleSuspenseList() { const { component, childManagers } = this; // $FlowFixMe: It comes here only for suspense list, so can be ignored const { revealOrder = 'together', tail } = component.props; // resolve the child managers based on reveal order const handleManagerInOrder = (promise, manager) => { return promise.then(() => { /** * If we are doing forward reveal order and have mentioned * tail to be collapsed we need to mark the next manager as dirty * so that the next component can show loading state */ if (revealOrder === 'forwards' && tail === 'collapsed') { markManagerDirty(manager); } return manager.handleSuspense(); }); }; /** * Create a promise which resolves after all the child managers are resolved */ const allSuspenderPromise = Promise.all(this.getChildrenSuspenders()); /** * If reveal order is together we resolve all the manager only * when all the suspenders are resolved. * * In case of forwards and backwards the managers need to resolved * in the provided order event the promise resolves concurrently */ if (revealOrder === 'together') { allSuspenderPromise.then(() => { childManagers.forEach((manager) => manager.handleSuspense()); }); } else if (revealOrder === 'forwards') { let promise = resolvedPromise; for (let i = 0, ln = childManagers.length; i < ln; i++) { promise = handleManagerInOrder(promise, childManagers[i]); } } else if (revealOrder === 'backwards') { let promise = resolvedPromise; for (let i = childManagers.length - 1; i >= 0; i--) { promise = handleManagerInOrder(promise, childManagers[i]); } } return allSuspenderPromise; } } function getActiveTransition(component: SuspenseList): AnyTransition { const fiber = getCurrentComponentFiber(); let transition = getTransitionFromFiber(fiber, PREDEFINED_TRANSITION_DEFERRED); /** * If the transition is resolved and pendingSuspense does not include the instance * then use the predefined deferred transition as transition * This will happen only when called through handleSuspense */ if ( transition.transitionState === TRANSITION_STATE_RESOLVED && !transition.pendingSuspense.includes(component) ) { transition = PREDEFINED_TRANSITION_DEFERRED; } return transition; } export class SuspenseList extends Component { constructor(props: SuspenseListProps) { super(props); this.suspenseManagers = {}; } render() { return this.props.children; } } export class Suspense extends Component implements SuspenseInstance { constructor(props: SuspenseProps) { super(props); this.suspenseManagers = {}; } handleSuspender(suspender: Promise, suspenseFiber: Fiber) { /** * $FlowFixMe: We only care about custom transition in this function * For predefined transition we don't wait, and we don't have to mark them pending */ const transition: Transition = getActiveTransition(this); const suspenseManager = getSuspenseManager(suspenseFiber, transition); /** * Mark current transition as suspended * only if transition is not completed or timed out. */ if (!isTransitionCompleted(transition)) { /** * Add current suspense to pending suspense */ if (!transition.pendingSuspense.includes(this)) { transition.pendingSuspense.push(this); } // Mark the transition as suspended transition.transitionState = TRANSITION_STATE_SUSPENDED; } suspenseManager.suspend(suspender); } render() { const { fallback, children } = this.props; const transition = getActiveTransition(this); const fiber = getCurrentComponentFiber(); const suspenseManager = getSuspenseManager(fiber, transition); if (suspenseManager.shouldRenderChildren()) return children; else if (suspenseManager.shouldShowFallback()) return fallback; else return null; } } export const lazy = (lazyCallback: () => Promise) => { let componentSuspender; const LazyComponent: FunctionalComponent = forwardRef((props, ref) => { const ComponentModule = componentSuspender.read(); // $FlowFixMe: lazy return createBrahmosNode(ComponentModule.default, { ...props, ref: ref }); }); // assign a method to lazy load to start loading during createBrahmosNode call LazyComponent.__loadLazyComponent = () => { if (!componentSuspender) componentSuspender = getPromiseSuspendedValue(lazyCallback()); }; return LazyComponent; }; ================================================ FILE: src/TagNode.js ================================================ // @flow import { addDataContainer } from './utils'; import type { BrahmosNode, TagNodeType, NodePart } from './flow.types'; /** * Generate a similar structure as Template node from BrahmosTagElement, * so that it can be processed in a same way tagged template literals. * * For any tag element, there are only two parts attributes and children. * So create parts based on that information one for attribute and one for children node, * And this parts will be pointing to two values [attributes, children]; */ export default function getTagNode(node: BrahmosNode, isSvgPart: boolean): TagNodeType { const { type } = node; const domNode = isSvgPart ? document.createElementNS('http://www.w3.org/2000/svg', type) : document.createElement(type); addDataContainer(domNode); const nodePart: NodePart = { previousSibling: null, parentNode: domNode, isNode: true, }; return { fragment: [domNode], domNodes: [domNode], parts: [nodePart], }; } ================================================ FILE: src/TemplateNode.js ================================================ // @flow import { BRAHMOS_PLACEHOLDER } from './configs'; import { toArray, createEmptyTextNode, addDataContainer, remove } from './utils'; import type { TemplateNodeType, TemplateTagType, Part, NodePart } from './flow.types'; function isBrahmosCommentNode(node: ?Node): boolean { return !!node && node.nodeType === 8 && node.textContent === BRAHMOS_PLACEHOLDER; } export default class TemplateNode implements TemplateNodeType { templateResult: TemplateTagType; fragment: DocumentFragment; parts: Array; domNodes: Array; patched: boolean; constructor(templateResult: TemplateTagType, isSvgPart: boolean) { this.templateResult = templateResult; // create the template first time the element is used templateResult.create(isSvgPart); // create dom fragment out of template this.fragment = this.createNode(isSvgPart); this.parts = this.getParts(); // keep the reference of child nodes // TODO: Check if you want to use Array.from instead this.domNodes = toArray(this.fragment.childNodes); this.patched = false; } createNode(isSvgPart: boolean): DocumentFragment { const { template, svgTemplate } = this.templateResult; const templateElement = isSvgPart ? svgTemplate : template; // $FlowFixMe: createNode will be called only after create method call, so templateElement will always be present return document.importNode(templateElement.content, true); } getParts(): Array { const { fragment, templateResult } = this; const { partsMeta } = templateResult; const parts = []; // get the flattened list of elements. const elements = fragment.querySelectorAll('*'); // generate part information which actual dom nodes which will be added in dom // eslint-disable-next-line no-unmodified-loop-condition for (let i = 0, ln = partsMeta.length; i < ln; i++) { const partMeta = partsMeta[i]; const { isAttribute, attrIndex, refNodeIndex, prevChildIndex, hasExpressionSibling, } = partMeta; let refNode = elements[refNodeIndex]; if (isAttribute) { // cache the tagAttribute calculate in templateTag part object, so we don't need to calculate it again. if (!partMeta.tagAttrs) partMeta.tagAttrs = toArray(refNode.attributes); parts.push({ isAttribute: true, tagAttrs: partMeta.tagAttrs, domNode: refNode, attrIndex, }); addDataContainer(refNode); } else { refNode = refNode || fragment; const hasPreviousSibling = prevChildIndex !== -1; let previousSibling; /** * If we get a dynamic part between two consecutive text node, * that gets combined as single text node when we create template element with static part. * For that we add an extra comment node during the compile time (babel-plugin), so they don't mix. * On runtime we need to check if that comment node is present, and if present remove the comment node. */ const possibleCommentNode = refNode.childNodes[prevChildIndex + 1]; if (hasPreviousSibling && isBrahmosCommentNode(possibleCommentNode)) { remove(possibleCommentNode); } if (!hasPreviousSibling) { previousSibling = null; } else if (hasExpressionSibling) { /** * If there are two consecutive dynamic parts, we add an empty text node, * So it gets easier to locate any dynamic part there. */ previousSibling = createEmptyTextNode(refNode, prevChildIndex); } else { previousSibling = refNode.childNodes[prevChildIndex]; } parts.push({ isNode: true, parentNode: refNode, previousSibling, }); } } return parts; } patchParts(nodePart: NodePart) { const { parts } = this; const { parentNode, previousSibling } = nodePart; if (this.patched) return; for (let i = 0, ln = parts.length; i < ln; i++) { // $FlowFixMe: We only care of NodePart and isNode check will properly check for it const part: NodePart = parts[i]; if (part.isNode && part.parentNode instanceof DocumentFragment) { part.parentNode = parentNode; part.previousSibling = part.previousSibling || previousSibling; } } this.patched = true; } } ================================================ FILE: src/TemplateTag.js ================================================ // @flow import type { PartMeta, TemplateTagType } from './flow.types'; /** * Parts meta code looks like this. * typeCode|primaryIndex|secondaryIndex * * typeCode = 0 -> its an attribute part * typeCode = 1 | 2 -> Its and node part * typeCode = 2 -> Its has a previous sibling which is an expression * * For attributes * primaryIndex -> Index of element in a flattened element list, for which attribute belongs to. * secondaryIndex -> Dynamic attribute index. This is required to identify which is overriding which. * * For node * primaryIndex -> Index of parent element in a flattened element list. * secondaryIndex -> Index of previous children in the childNodes list of parent. */ function decodePartMeta(partMetaCode: string): Array { const parts = partMetaCode.split(','); return parts.map((partCodeStr) => { const [typeCode, primaryIndex, secondaryIndex] = partCodeStr.split('|'); const isAttribute = typeCode === '0'; const hasExpressionSibling = typeCode === '2'; return { isAttribute, refNodeIndex: primaryIndex ? Number(primaryIndex) : -1, attrIndex: isAttribute ? Number(secondaryIndex) : -1, prevChildIndex: !isAttribute && secondaryIndex ? Number(secondaryIndex) : -1, hasExpressionSibling, tagAttrs: undefined, }; }); } export default class TemplateTag implements TemplateTagType { $key: 'svgTemplate' | 'template'; $value: ?HTMLTemplateElement; strings: Array; template: ?HTMLTemplateElement; svgTemplate: ?HTMLTemplateElement; partsMeta: Array; partMetaCode: string; staticTree: any; constructor(strings: Array, partMetaCode: string) { this.strings = strings; this.template = null; this.svgTemplate = null; this.partsMeta = []; this.partMetaCode = partMetaCode; } create(isSvgPart: boolean) { if (isSvgPart && this.svgTemplate) return; if (this.template) return; if (!this.partsMeta.length) { this.partsMeta = decodePartMeta(this.partMetaCode); } this.createTemplate(isSvgPart); } createTemplate(isSvgPart: boolean) { const { strings } = this; const template = document.createElement('template'); const htmlStr = strings.join(''); /** * if its svg child wrap it inside svg * so that inner elements are parsed in svg context * Or else add the htmlStr directly */ template.innerHTML = isSvgPart ? `${htmlStr}` : htmlStr; /** * Once added to template unwrap the element from svg wrap */ if (isSvgPart) { const { content } = template; // $FlowFixMe: In this case there will always have a wrap. const svgWrap: SVGElement = content.firstChild; // move all children out of the element while (svgWrap.firstChild) content.insertBefore(svgWrap.firstChild, svgWrap); // remove the empty element content.removeChild(svgWrap); } const templateKey = isSvgPart ? 'svgTemplate' : 'template'; this[templateKey] = template; } } ================================================ FILE: src/__tests__/BrahmosES6class.test.js ================================================ /* Forked from - https://github.com/facebook/react/blob/master/packages/react/src/__tests__/ReactES6Class-test.js TODO: Simplify this specs. */ import Brahmos, { render } from '..'; import { sleep } from './testUtils'; describe('BrahmosES6Class', () => { let container; const freeze = function (expectation) { Object.freeze(expectation); return expectation; }; let Inner; let attachedListener = null; let attachedListenerWithCallback = null; let renderedName = null; beforeEach(() => { attachedListener = null; attachedListenerWithCallback = null; renderedName = null; container = document.createElement('div'); Inner = class extends Brahmos.Component { getName() { return this.props.name; } render() { attachedListenerWithCallback = (callback) => this.props.onClick(callback); attachedListener = this.props.onClick; renderedName = this.props.name; return
    ; } }; }); function test(element, expectedTag, expectedClassName) { const instance = render(element, container); expect(container.firstChild).not.toBeNull(); expect(container.firstChild.tagName).toBe(expectedTag); expect(container.firstChild.className).toBe(expectedClassName); return instance; } it('preserves the name of the class for use in error messages', () => { class Foo extends Brahmos.Component {} expect(Foo.name).toBe('Foo'); }); it('renders a simple stateless component with prop', () => { class Foo extends Brahmos.Component { render() { return ; } } test(, 'DIV', 'foo'); test(, 'DIV', 'bar'); }); it('renders based on state using initial values in this.props', () => { class Foo extends Brahmos.Component { constructor(props) { super(props); this.state = { bar: this.props.initialValue }; } render() { return ; } } test(, 'SPAN', 'foo'); }); it('renders based on state using props in the constructor', () => { class Foo extends Brahmos.Component { constructor(props) { super(props); this.state = { bar: props.initialValue }; } changeState() { this.setState({ bar: 'bar' }); } render() { if (this.state.bar === 'foo') { return
    ; } return ; } } const instance = test(, 'DIV', 'foo'); instance.changeState(); test(, 'SPAN', 'bar'); }); it('sets initial state with value returned by static getDerivedStateFromProps', () => { class Foo extends Brahmos.Component { state = {}; static getDerivedStateFromProps(nextProps, prevState) { return { foo: nextProps.foo, bar: 'bar', }; } render() { return
    ; } } test(, 'DIV', 'foo bar'); }); it('updates initial state with values returned by static getDerivedStateFromProps', () => { class Foo extends Brahmos.Component { state = { foo: 'foo', bar: 'bar', }; static getDerivedStateFromProps(nextProps, prevState) { return { foo: `not-${prevState.foo}`, }; } render() { return
    ; } } test(, 'DIV', 'not-foo bar'); }); it('renders updated state with values returned by static getDerivedStateFromProps', () => { class Foo extends Brahmos.Component { state = { value: 'initial', }; static getDerivedStateFromProps(nextProps, prevState) { if (nextProps.update) { return { value: 'updated', }; } return null; } render() { return
    ; } } test(, 'DIV', 'initial'); test(, 'DIV', 'updated'); }); it('should render with null in the initial state property', () => { class Foo extends Brahmos.Component { constructor() { super(); this.state = null; } render() { return ; } } test(, 'SPAN', ''); }); it('setState through an event handler', () => { class Foo extends Brahmos.Component { constructor(props) { super(props); this.state = { bar: props.initialValue }; } handleClick(callback) { this.setState({ bar: 'bar' }, () => callback()); } render() { return ; } } test(, 'DIV', 'foo'); attachedListenerWithCallback(() => expect(renderedName).toBe('bar')); // Passed the test as a callback }); it('should not implicitly bind event handlers', () => { class Foo extends Brahmos.Component { constructor(props) { super(props); this.state = { bar: props.initialValue }; } handleClick() { this.setState({ bar: 'bar' }); } render() { return ; } } test(, 'DIV', 'foo'); expect(attachedListener).toThrow(); }); it('will call all the normal life cycle methods', () => { let lifeCycles = []; class Foo extends Brahmos.Component { constructor() { super(); this.state = {}; } componentDidMount() { lifeCycles.push('did-mount'); } shouldComponentUpdate(nextProps, nextState) { lifeCycles.push('should-update', nextProps, nextState); return true; } componentDidUpdate(prevProps, prevState) { lifeCycles.push('did-update', prevProps, prevState); } componentWillUnmount() { lifeCycles.push('will-unmount'); } render() { return ; } } class Outer extends Brahmos.Component { constructor(props) { super(props); this.state = { isFooVisible: this.props.visible, }; } unmountFoo(callback) { this.setState( { isFooVisible: false, }, () => callback, ); } render() { if (this.state.isFooVisible) { return ; } return
    ; } } test(, 'SPAN', 'foo'); expect(lifeCycles).toEqual(['did-mount']); lifeCycles = []; // reset const instance = test(, 'SPAN', 'bar'); expect(lifeCycles).toEqual([ 'should-update', freeze({ value: 'bar' }), {}, 'did-update', freeze({ value: 'foo' }), {}, ]); lifeCycles = []; // reset instance.unmountFoo(() => expect(lifeCycles).toEqual(['will-unmount'])); }); it('renders using forceUpdate even when there is no state', async () => { class Foo extends Brahmos.Component { constructor(props) { super(props); this.mutativeValue = props.initialValue; } handleClick(callback) { this.mutativeValue = 'bar'; this.forceUpdate(() => callback()); } render() { return ; } } test(, 'DIV', 'foo'); attachedListenerWithCallback(() => {}); await sleep(10); expect(renderedName).toBe('bar'); }); }); ================================================ FILE: src/__tests__/BrahmosPureComponent.test.js ================================================ /* Forked from - https://github.com/facebook/react/blob/master/packages/react/src/__tests__/ReactPureComponent-test.js */ import Brahmos, { render } from '..'; describe('BrahmosPureComponent', () => { it('should re-render only when old and new props or state are not shallow equal', (done) => { let renders = 0; class Component extends Brahmos.PureComponent { constructor() { super(); this.state = { type: 'mushrooms' }; } render() { renders++; return
    {this.props.text[0]}
    ; } } const container = document.createElement('div'); let text; let component; text = ['porcini']; component = render(, container); expect(container.textContent).toBe('porcini'); expect(renders).toBe(1); text = ['morel']; component = render(, container); expect(container.textContent).toBe('morel'); expect(renders).toBe(2); text[0] = 'portobello'; component = render(, container); expect(container.textContent).toBe('morel'); expect(renders).toBe(2); // Setting state without changing it doesn't cause a rerender. component.setState({ type: 'mushrooms' }, () => { expect(container.textContent).toBe('morel'); expect(renders).toBe(2); // But changing state does. component.setState({ type: 'portobello mushrooms' }, () => { expect(container.textContent).toBe('portobello'); expect(renders).toBe(3); done(); }); }); }); it('extends Brahmos.Component', () => { let renders = 0; class Component extends Brahmos.PureComponent { constructor() { super(); // Doesn't render if constructor is removed } render() { renders++; return
    ; } } const pureComponentInstance = render(, document.createElement('div')); expect(pureComponentInstance instanceof Brahmos.Component).toBe(true); expect(pureComponentInstance instanceof Brahmos.PureComponent).toBe(true); expect(renders).toBe(1); }); }); ================================================ FILE: src/__tests__/__snapshots__/listRendering.test.js.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Test list rendering Test Stateful Components in Array should handle adding item on state full component which is part of an array. 1`] = ` "
  • a
  • b0
  • b1
  • b2
  • c0
  • c1
  • d0
  • d1
  • e
  • " `; exports[`Test list rendering Test Stateful Components in Array should handle removing item on state full component which is part of an array. 1`] = ` "
  • a
  • b0
  • b1
  • c0
  • d0
  • d1
  • e
  • " `; exports[`Test list rendering Test Stateful Components in Array should handle shuffle of items on state full component which is part of an array. 1`] = ` "
  • a
  • b0
  • b1
  • c0
  • c1
  • d1
  • d0
  • e
  • " `; exports[`Test list rendering Test Stateful Components in Array should render nested elements in correct order 1`] = ` "
  • a
  • b0
  • b1
  • c0
  • c1
  • d0
  • d1
  • e
  • " `; ================================================ FILE: src/__tests__/attributes.test.js ================================================ import { fireEvent } from '@testing-library/dom'; import { createContext, useState } from '../'; import { useContext } from '../brahmos'; import { render, unmountAll, sleep } from './testUtils'; describe('Test attribute', () => { const TestContext = createContext(); const { Provider, Consumer } = TestContext; it('should allow passing null on style prop', async () => { function Test(style) { return
    Hell World
    ; } expect(() => render()).not.toThrow(); }); }); ================================================ FILE: src/__tests__/children_apis.test.js ================================================ import { cloneElement, Children } from '../'; import { render } from './testUtils'; describe('Test cloneElement', () => { const Test = (props) => props.children; const element = Hello World!; it('should not change any thing if props are not provided', () => { const elementClone = cloneElement(element); expect(elementClone).not.toStrictEqual(element); expect(elementClone.props).toEqual({ type: 'welcome', children: 'Hello World!', }); }); it('should update props when replacement prop is provided', () => { // update props partially expect(cloneElement(element, { type: 'greet' }).props).toEqual({ type: 'greet', children: 'Hello World!', }); // update props along with children expect(cloneElement(element, { type: 'greet', children: 'Hello Brahmos!' }).props).toEqual({ type: 'greet', children: 'Hello Brahmos!', }); }); it('should update children when replacement children is provided', () => { expect(cloneElement(element, { type: 'greet' }, 'Hello Brahmos!').props).toEqual({ type: 'greet', children: 'Hello Brahmos!', }); // passing undefined should replace child expect(cloneElement(element, { type: 'greet' }, undefined).props).toEqual({ type: 'greet', children: undefined, }); // should allow passing multiple children expect(cloneElement(element, { type: 'greet' }, 'Hello', 'Brahmos!').props).toEqual({ type: 'greet', children: ['Hello', 'Brahmos!'], }); }); }); describe('Test Children [forEach, map, count]', () => { const Child = ({ children }) => {children}; it('should loop correctly on the flattened children', () => { const Parent = ({ children }) => { const count = Children.count(children); let forEachCount = 0; Children.forEach(children, () => { forEachCount += 1; }); const modifiedChildren = Children.map(children, (child) => { return cloneElement(child, { children: 2 }); }); return (
    {modifiedChildren}
    {count}
    {forEachCount}
    ); }; const { container } = render( 1 <> 1 1 {[1, 1].map((value) => ( {value} ))} , ); expect(container.querySelector('.count').textContent).toEqual('5'); expect(container.querySelector('.foreach-count').textContent).toEqual('5'); expect(container.querySelector('.child-wrap').textContent).toEqual('22222'); }); }); ================================================ FILE: src/__tests__/context.test.js ================================================ import { fireEvent } from '@testing-library/dom'; import { createContext, useState } from '..'; import { useContext } from '../brahmos'; import { render, sleep, unmountAll } from './testUtils'; describe('Test context API', () => { const TestContext = createContext(); const { Provider, Consumer } = TestContext; beforeEach(unmountAll); it('should rerender Consumers if Provider passes new data and Consumer is not the direct child', async () => { const Parent = ({ children }) => { const [count, setCounter] = useState(1); return ( {children} ); }; const Button = () => { // Try the useContext hook const { count, setCounter } = useContext(TestContext); return ; }; const Value = () => { // Try the hoc based consumer return ( {(countContext) => { return {countContext.count}; }} ); }; const Wrap = ({ children }) => { return
    {children}
    ; }; const { container } = render( , ); } else { items.push(
  • , ); } Array.from({ length: count }).forEach((val, index) => { items.push(); }); if (reverseList) items.reverse(); return items; }; let container; beforeEach(() => { ({ container } = render(
    {[ , , , , , ]}
    , )); }); afterEach(unmountAll); it('should render nested elements in correct order', () => { expect(html(container.innerHTML)).toMatchSnapshot(); }); it('should handle adding item on state full component which is part of an array.', async () => { // should handle adding elements in between fireEvent.click(container.getByTestId('bcount')); // wait for current stack to empty. await 1; expect(container.getByText('b2')).not.toBeNull(); expect(html(container.innerHTML)).toMatchSnapshot(); }); it('should handle removing item on state full component which is part of an array.', async () => { // should handle deleting elements in between fireEvent.click(container.getByTestId('ccount')); // wait for current stack to empty. await 1; // expect(container.getByText('c1')).toBeNull(); expect(html(container.innerHTML)).toMatchSnapshot(); }); it('should handle shuffle of items on state full component which is part of an array.', async () => { // should handle deleting elements in between fireEvent.click(container.getByTestId('dreverse')); // wait for current stack to empty. await 1; // expect(container.getByText('c1')).toBeNull(); expect(html(container.innerHTML)).toMatchSnapshot(); }); }); }); ================================================ FILE: src/__tests__/memo.test.js ================================================ import { fireEvent } from '@testing-library/dom'; import { memo, useState } from '../'; import { render, sleep } from './testUtils'; describe('Test memoization', () => { it('should rerender on state change in memoized functional component ', async () => { const Test = memo(() => { const [value, setValue] = useState(1); return (
    {value}
    ); }); const { container } = render(); fireEvent.click(container.querySelector('button')); await sleep(0); expect(container.querySelector('.value').textContent).toEqual('2'); }); it('should work for multiple memoized instance ', async () => { const Test = memo(({ onRender, testId }) => { onRender(); return
    Hello World
    ; }); let renderCount1 = 0; let renderCount2 = 0; const onRender1 = () => (renderCount1 += 1); const onRender2 = () => (renderCount2 += 1); const { update: update1 } = render(); const { update: update2 } = render(); expect(renderCount1).toEqual(1); expect(renderCount2).toEqual(1); // try re rendering with same props, render count should not change update1({ onRender: onRender1, testId: 0 }); expect(renderCount1).toEqual(1); // try re rendering with other props, render count should change update2({ onRender: onRender2, testId: 1 }); expect(renderCount2).toEqual(2); }); }); ================================================ FILE: src/__tests__/setState.test.js ================================================ import { fireEvent } from '@testing-library/dom'; import { html } from 'js-beautify'; import { Component } from '../'; import { render, unmountAll, sleep } from './testUtils'; describe('Test setState', () => { it('should handle setState in componentDidMount in multiple level', async () => { class Child extends Component { state = { value: 1, }; componentDidMount() { this.setState({ value: 2 }); } render() { return (
    {this.state.value}
    ); } } class Parent extends Component { state = { value: 1, }; componentDidMount() { this.setState({ value: 2 }); } render() { return (
    {this.state.value}
    ); } } // we await as set states are happened on next micro task const { container } = await render(); expect(container.textContent).toEqual('22'); }); it('should give correct value of state inside setState callback ', async () => { let effectiveState = 0; class Test extends Component { state = { value: 1, }; componentDidMount() { this.setState({ value: 2 }, () => { effectiveState = this.state.value; }); } render() { return (
    {this.state.value}
    ); } } // we await as set states are happened on next micro task await render(); await sleep(0); expect(effectiveState).toEqual(2); }); it('should not cause multiple repaint if another setState happens in setState callback', async () => { class Test extends Component { state = { value: 1, }; onClick = () => { this.setState({ value: 2 }, () => { this.setState({ value: 3 }); }); }; render() { return (
    {this.state.value}
    ); } } const { container } = await render(); fireEvent.click(container.getByTestId('button')); await sleep(0); expect(container.querySelector('.value').textContent).toEqual('3'); }); it('should not cause multiple repaint if another setState happens in componentDidMount/componentDidUpdate', async () => { class Test extends Component { state = { value: 1, }; componentDidUpdate() { if (this.state.value !== 3) { this.setState({ value: 3 }); } } render() { return (
    {this.state.value}
    ); } } const { container, ref } = await render(); ref.setState({ value: 2 }); /** * We are trying to simulate actual repaint case. * await for micro task as setState updates are batched */ await 1; await sleep(0); expect(container.querySelector('.value').textContent).toEqual('3'); }); }); ================================================ FILE: src/__tests__/testUtils.js ================================================ import * as testLib from '@testing-library/dom'; import { render, unmountComponentAtNode } from '..'; const queryMethodRegex = /^(getBy|findBy|queryBy|getAllBy|findAllBy|queryAllBy)/; const containersList = []; export function unmountAll() { containersList.forEach(unmountComponentAtNode); } function renderNode(node) { const container = document.createElement('div'); const ref = render(node, container); const methodCache = {}; containersList.push(container); const containerProxy = new Proxy(container, { get(target, key, receiver) { if (typeof key === 'string' && key.match(queryMethodRegex)) { methodCache[key] = methodCache[key] || testLib[key].bind(null, container); return methodCache[key]; } return Reflect.get(...arguments); }, }); return { container: containerProxy, ref, update: (newProps) => render({ ...node, props: newProps }, container), unmount: () => unmountComponentAtNode(container), }; } export function sleep(time) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(); }, time); }); } export { renderNode as render }; ================================================ FILE: src/__tests__/utils.test.js ================================================ import { isHTMLElement, isEventAttribute, getNodeName, getEventName, isCustomElement, } from '../utils'; describe('Test utility functions', () => { describe('Test isHTMLElement', () => { it('should return true for valid HTML element tag', () => { expect(isHTMLElement('a')).toBe(true); expect(isHTMLElement('span')).toBe(true); expect(isHTMLElement('div')).toBe(true); }); it('should return false for invalid HTML element tag', () => { expect(isHTMLElement('CustomComponent')).toBe(false); expect(isHTMLElement('_Component')).toBe(false); }); }); describe('Test isEventAttribute', () => { it('should return true if valid event attribute', () => { expect(isEventAttribute('onChange')).toBe(true); expect(isEventAttribute('onLoad')).toBe(true); expect(isEventAttribute('onHover')).toBe(true); }); it('should return false if invalid event attribute', () => { expect(isEventAttribute('change')).toBe(false); expect(isEventAttribute('load')).toBe(false); expect(isEventAttribute('hover')).toBe(false); }); }); describe('Test getNodeName', () => { it('should return name of the current node', () => { const node = document.createElement('a'); expect(getNodeName(node)).toBe('a'); const text = document.createTextNode('some text'); expect(getNodeName(text)).toBe('#text'); }); }); describe('Test getEventName', () => { it('should return event name from handler attribute', () => { expect(getEventName('onChange')).toBe('change'); expect(getEventName('onKeyDown')).toBe('keydown'); }); }); describe('Test isCustomElement', () => { it('should return true for custom web components', () => { expect(isCustomElement('web-component')).toBe(true); }); it('should return false for components and native elements', () => { expect(isCustomElement('span')).toBe(false); expect(isCustomElement('MyComponent')).toBe(false); }); }); }); ================================================ FILE: src/brahmos.js ================================================ /** Component classes, Suspense and lazy */ import { Component, PureComponent, createElement, createBrahmosNode, Suspense, SuspenseList, lazy, } from './circularDep'; import { html } from './tags'; /** render methods */ import render from './render'; /** Portal */ import createPortal from './createPortal'; /** Hooks */ import { useState, useEffect, useRef, useReducer, useMemo, useCallback, useLayoutEffect, useContext, useTransition, useDeferredValue, useDebugValue, } from './hooks'; /** createContext */ import createContext from './createContext'; /** ForwardRef and createRef */ import { forwardRef, createRef } from './refs'; /** unmountComponentAtNode */ import unmountComponentAtNode from './unmountComponentAtNode'; /** unstableBatchedUpdate */ import { deferredUpdates, syncUpdates } from './updateUtils'; /** import top level api */ import { Children, isValidElement, cloneElement } from './Children'; import memo from './memo'; const NoopComponent = (props) => props.children; // add a noop component for StrictMode and Fragment, as brahmos don't support any deprecated API const StrictMode = NoopComponent; const Fragment = NoopComponent; /** * Mock for unstable_batchedUpdates as third party lib like Redux uses it. * Brahmos by default batches update so we can just call the passed callback directly */ function unstable_batchedUpdates(cb) { cb(); } export { createElement, render, Component, PureComponent, useState, useEffect, useRef, useReducer, useMemo, useCallback, useLayoutEffect, useContext, useTransition, useDeferredValue, useDebugValue, createContext, forwardRef, createRef, createPortal, unmountComponentAtNode, Suspense, SuspenseList, lazy, Children, isValidElement, cloneElement, deferredUpdates as unstable_deferredUpdates, syncUpdates as unstable_syncUpdates, unstable_batchedUpdates, memo, StrictMode, Fragment, }; /** Export transforms */ export const jsx = createBrahmosNode; export const jsxs = createBrahmosNode; export const jsxDev = createBrahmosNode; export { html }; ================================================ FILE: src/brahmosNode.js ================================================ // @flow import { isNil } from './utils'; import type { BrahmosNode, ObjectLiteral } from './flow.types'; import { REACT_ELEMENT } from './configs'; export const TAG_NODE = Symbol.for('tag'); export const TAG_ELEMENT_NODE = Symbol.for('tag-element'); export const CLASS_COMPONENT_NODE = Symbol.for('class-component'); export const FUNCTIONAL_COMPONENT_NODE = Symbol.for('functional-component'); export const ATTRIBUTE_NODE = Symbol.for('attribute'); type NotNil = $NonMaybeType; // $FlowFixMe: As we are just comparing a property, on any type of non nil node export function isTagElementNode({ nodeType }: NotNil): boolean { return nodeType === TAG_ELEMENT_NODE; } // $FlowFixMe: As we are just comparing a property, on any type of non nil node export function isHtmlTagNode({ nodeType }: NotNil): boolean { return nodeType === TAG_NODE; } export function isTagNode(node: NotNil): boolean { return isTagElementNode(node) || isHtmlTagNode(node); } // $FlowFixMe: As we are just comparing a property, on any type of non nil node export function isComponentNode({ nodeType }: NotNil): boolean { return nodeType === CLASS_COMPONENT_NODE || nodeType === FUNCTIONAL_COMPONENT_NODE; } export function isBrahmosNode(node: any): boolean { return !!node && (isTagNode(node) || isComponentNode(node)); } /** * Function to check if a node should be rendered as string */ export function isPrimitiveNode(node: any): boolean { return typeof node === 'string' || typeof node === 'number'; } /** * Function to check if node can be rendered or not */ export function isRenderableNode(node: any): boolean { return !(isNil(node) || typeof node === 'boolean'); } /** * Get the key of looped node */ export function getKey(node: BrahmosNode, index: number): number | string { const key = node && node.key; /** * if key is defined use key or else use index as key. */ return key === undefined ? index : key; } export function brahmosNode(props: ?ObjectLiteral, values: ?Array, key?: string): BrahmosNode { return { $$typeof: REACT_ELEMENT, /** Common node properties */ nodeType: null, key, ref: null, portalContainer: null, /** Component specific properties */ type: null, props, /** tag node specific properties */ values, template: null, }; } ================================================ FILE: src/circularDep.js ================================================ /** * The following components cause circular dependency * issue in webpack, so to mitigate that the order of * import in bundler is forced through this */ export * from './Component'; export * from './createElement'; export * from './Suspense'; ================================================ FILE: src/configs.js ================================================ // @flow // React element types // We have to add same type of to support third party plugins export const REACT_ELEMENT = Symbol.for('react.element'); export const REACT_FORWARD_REF = Symbol.for('react.forward_ref'); // Brahmos placeholder export const BRAHMOS_PLACEHOLDER = '{{brahmos}}'; // reserved props which cannot be forward to component props export const RESERVED_ATTRIBUTES = { key: 1, ref: 1, }; export const MODIFIED_ATTRIBUTES: { [key: string]: string } = { className: 'class', htmlFor: 'for', acceptCharset: 'accept-charset', httpEquiv: 'http-equiv', tabIndex: 'tabindex', // tabIndex is supported both on svg and html, but in svg the camelCase does not work. So transform this }; export const RENAMED_EVENTS = { doubleclick: 'dblclick', }; /** * Regex taken from Preact. (https://github.com/preactjs/preact/blob/master/compat/src/render.js) */ // Input types for which onchange should not be converted to oninput. // type="file|checkbox|radio", plus "range" in IE11. // (IE11 doesn't support Symbol, which we use here to turn `rad` into `ra` which matches "range") export const ONCHANGE_INPUT_TYPES = typeof Symbol !== 'undefined' ? /fil|che|rad/i : /fil|che|ra/i; export const CAMEL_ATTRIBUTES = /^(?:accent|alignment|arabic|baseline|cap|clip(?!PathU)|color|fill|flood|font|glyph(?!R)|horiz|marker(?!H|W|U)|overline|paint|stop|strikethrough|stroke|text(?!L)|underline|unicode|units|v|vector|vert|word|writing|x(?!C))[A-Z]/; /** * Regex taken from Preact. (https://github.com/preactjs/preact/blob/master/src/constants.js) */ export const IS_NON_DIMENSIONAL = /acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|^--/i; /** * xlink namespace for svgs */ export const XLINK_NS = 'http://www.w3.org/1999/xlink'; export const SUSPENSE_REVEAL_INTERVAL = 100; // in ms /** * Brahmos data key which shouldn't be touched */ export const BRAHMOS_DATA_KEY: '__brahmosData' = '__brahmosData'; export const LAST_ARRAY_DOM_KEY: '__brahmosLastArrayDom' = '__brahmosLastArrayDom'; export const ROOT_FIBER_KEY: '__rootFiber' = '__rootFiber'; /** update type related constants */ export const UPDATE_TYPE_SYNC: 'sync' = 'sync'; export const UPDATE_TYPE_DEFERRED: 'deferred' = 'deferred'; /** Update source related constants */ export const UPDATE_SOURCE_DEFAULT: 'js' = 'js'; export const UPDATE_SOURCE_IMMEDIATE_ACTION: 'immediate_action' = 'immediate_action'; export const UPDATE_SOURCE_TRANSITION: 'transition' = 'transition'; /** Effect type ENUMS */ export const EFFECT_TYPE_NONE: 0 = 0; export const EFFECT_TYPE_PLACEMENT: 1 = 1; export const EFFECT_TYPE_OTHER: 2 = 2; /** Transition states */ export const TRANSITION_STATE_INITIAL: 'initial' = 'initial'; export const TRANSITION_STATE_START: 'start' = 'start'; export const TRANSITION_STATE_SUSPENDED: 'suspended' = 'suspended'; export const TRANSITION_STATE_RESOLVED: 'resolved' = 'resolved'; export const TRANSITION_STATE_COMPLETED: 'completed' = 'completed'; export const TRANSITION_STATE_TIMED_OUT: 'timedOut' = 'timedOut'; ================================================ FILE: src/createContext.js ================================================ // @flow import { Component } from './circularDep'; import { BRAHMOS_DATA_KEY } from './configs'; import { setUpdateTime, getFiberFromComponent } from './fiber'; import type { Fiber, AnyComponentInstance, ContextType } from './flow.types'; type ConsumerCallbackReturn = (value: any) => void; let ctxId = 1; export function getConsumerCallback(component: AnyComponentInstance): ConsumerCallbackReturn { return function (value: any): void { /** * just set the correct update time on subscribed component, * and then workloop will take care of updating them. */ const fiber: Fiber = getFiberFromComponent(component); const { updateType } = fiber.root; // update time only when context value has been changed if (component.context !== value) { // mark consumer dirty component[BRAHMOS_DATA_KEY].isDirty = true; setUpdateTime(fiber, updateType); } }; } export default function createContext(defaultValue: any): ContextType { const id = `cC${ctxId++}`; class Provider extends Component { constructor(props) { super(props); this.subs = []; } shouldComponentUpdate(nextProp) { const { value } = this.props; if (value !== nextProp.value) { this.subs.forEach((cb) => cb(nextProp.value)); } return true; } sub(component) { const { subs } = this; const callback = getConsumerCallback(component); subs.push(callback); const { componentWillUnmount } = component; component.componentWillUnmount = () => { subs.splice(subs.indexOf(callback), 1); if (componentWillUnmount) componentWillUnmount(); }; } render() { return this.props.children; } } // add metadata for provider Provider.__ccId = id; /** * consumer component which subscribes to provider on initialization * and unsubscribe on component unmount */ class Consumer extends Component { render() { return this.props.children(this.context); } } const context = { id, defaultValue, Provider, Consumer, }; // add contextType information on Consumer Consumer.contextType = context; return context; } ================================================ FILE: src/createElement.js ================================================ // @flow import { getNormalizedProps, toArray } from './utils'; import { brahmosNode, TAG_ELEMENT_NODE, CLASS_COMPONENT_NODE, FUNCTIONAL_COMPONENT_NODE, } from './brahmosNode'; import type { BrahmosNode, Ref } from './flow.types'; type Configs = { key: string, ref: Ref, children: any }; export function createBrahmosNode(element: string | Function, props: Configs, key: string) { const { ref } = props; // There can be chances key might be in props, if key is not passed as arg try to find it in props if (key === undefined) key = props.key; // remove key and ref property from props if present // $FlowFixMe: It's okay to access __isForwardRef always. props = getNormalizedProps(props, element.__isForwardRef); const node = brahmosNode(props, null, key); node.type = element; /** * If the create element is receiving an string element it means it is not a component, * but a simple tag instead. In that case set the nodeType to tagElement */ if (typeof element === 'string') { node.nodeType = TAG_ELEMENT_NODE; node.ref = ref; return node; } // otherwise if its a component handle the component node const _isClassComponent = element.prototype && element.prototype.isReactComponent; node.nodeType = _isClassComponent ? CLASS_COMPONENT_NODE : FUNCTIONAL_COMPONENT_NODE; // Add ref for class component node.ref = _isClassComponent ? ref : null; // if the element is a lazy component, start fetching the underlying component if (element.__loadLazyComponent) element.__loadLazyComponent(); // add default props values const defaultProps = typeof element === 'function' && element.defaultProps; if (defaultProps) { for (key in defaultProps) { if (props[key] === undefined) props[key] = defaultProps[key]; } } return node; } export function createElement( element: string | Function, props: Configs, children: any, ): BrahmosNode { props = props || {}; /** * If there is single children no need to keep it as an array */ const argLn = arguments.length; const _children = argLn > 3 ? toArray(arguments, 2) : children; // add children to props if (_children) props.children = _children; return createBrahmosNode(element, props, props.key); } ================================================ FILE: src/createPortal.js ================================================ // @flow import type { BrahmosNode } from './flow.types'; /** * Render children outside the main DOM hierarchy of * the parent component without losing the context */ function createPortal(child: ?BrahmosNode, container: HTMLElement) { // add portal container information in child // which we can use for forming the part if (child) child.portalContainer = container; return child; } export default createPortal; ================================================ FILE: src/effectLoop.js ================================================ // @flow import { isTagElementNode, isTagNode, isComponentNode, isPrimitiveNode, ATTRIBUTE_NODE, CLASS_COMPONENT_NODE, } from './brahmosNode'; import { callLifeCycle, insertBefore, getNextSibling } from './utils'; import { getTransitionFromFiber } from './transitionUtils'; import { getPendingUpdatesKey } from './updateUtils'; import { runEffects, cleanEffects } from './hooks'; import updateNodeAttributes from './updateAttribute'; import { EFFECT_TYPE_NONE, BRAHMOS_DATA_KEY, UPDATE_TYPE_DEFERRED, LAST_ARRAY_DOM_KEY, EFFECT_TYPE_OTHER, UPDATE_TYPE_SYNC, } from './configs'; import { getUpdateTimeKey, getLastCompleteTimeKey } from './fiber'; import { setRef } from './refs'; import type { HostFiber, Fiber, Part, NodePart, ArrayPart, TemplateNodeType, ExtendedElement, } from './flow.types'; /** * Updater to handle text node */ function updateTextNode(fiber: Fiber): void { const { node, alternate } = fiber; // $FlowFixMe: We come to this method only as node part/array part so we don't need to handle Attribute part const part: ArrayPart | NodePart = fiber.part; const { parentNode, previousSibling } = part; /** * Get the next sibling before which we need to append the text node. */ const nextSibling = getNextSibling(parentNode, previousSibling); /** * The nextSibling will point to text node if the tag fiber is already rendered * In which case we just have to update the node value of the nextSibling */ if (alternate && nextSibling) { // if we have text node just update the text node nextSibling.nodeValue = node; } else { // add nodes at the right location insertBefore(parentNode, nextSibling, node); } } function getTagChild(fiber: Fiber): Fiber { while (fiber.child && fiber.node && !isTagNode(fiber.node)) fiber = fiber.child; return fiber; } function setLastItemInParentDOM(parentNode: ExtendedElement, nodeInstance: TemplateNodeType): void { const { domNodes } = nodeInstance; parentNode[LAST_ARRAY_DOM_KEY] = domNodes[domNodes.length - 1]; } /** * For array item we need to store first element reference in the part of parent fiber. * This is required to calculate correct prev sibling of first dom node in the list . */ function setFirstNodeReference(part: Part, firstDOMNode: Node) { if (part.isArrayNode && part.nodeIndex === 0) { // $FlowFixMe: the while loop here checks if part is not undefined while ((part = part.parentArrayPart)) { part.firstDOMNode = firstDOMNode; } } } function getCorrectPreviousSibling(part: NodePart | ArrayPart): ?Node { let { previousSibling } = part; /** * When the part is a array part we might not have correct previous sibling * In such case we have different ways to find the previous sibling */ if (part.isArrayNode) { const { firstDOMNode, nodeIndex } = part; /** * if the the nodeIndex is > 0 then we can always rely on the LAST_ARRAY_DOM_KEY, * As during mount and update we maintain this information. */ if (nodeIndex > 0) { previousSibling = part.parentNode[LAST_ARRAY_DOM_KEY]; } else if (part.parentArrayPart) { /** * If the part has parentArrayPart. It means there is array inside array. * With that there can be two cases. * * 1. On mount phase where the elements are not inserted yet, we will not have first node info. * In which case we want to rely on LAST_ARRAY_DOM_KEY on parent element. * * 2. On update we will always have firstDOMNode reference, in this case we find the previousSibling * using firstDOMNode, so we always get the correct prevSibling of the array part. */ previousSibling = firstDOMNode ? firstDOMNode.previousSibling : part.parentNode[LAST_ARRAY_DOM_KEY]; } } return previousSibling; } function reArrangeExistingNode(fiber: Fiber, alternate: Fiber): void { // $FlowFixMe: We only handle ArrayPart in this function so can ignore other types const { part }: { part: ArrayPart } = fiber; if (!part.isArrayNode) return; const { nodeIndex, parentNode } = part; // $FlowFixMe: We only handle ArrayPart in this function so can ignore other types const oldNodeIndex = alternate.part.nodeIndex; const tagChild = getTagChild(fiber); const { nodeInstance } = tagChild; // if there is no nodeInstance or if tagChild has pendingEffects bail out from rearrange in component level const componentChildHasEffect = tagChild !== fiber && tagChild.hasUncommittedEffect; if (!nodeInstance || componentChildHasEffect) return; // if the item position on last render and current render is not same, then do a rearrange if (nodeIndex !== oldNodeIndex) { const { domNodes } = nodeInstance; const previousSibling = getCorrectPreviousSibling(part); const nextSibling = getNextSibling(parentNode, previousSibling); // if there is dom node and it isn't in correct place rearrange the nodes const firstDOMNode = domNodes[0]; if ( firstDOMNode && firstDOMNode.previousSibling !== previousSibling && firstDOMNode !== nextSibling ) { insertBefore(parentNode, nextSibling, domNodes); } setFirstNodeReference(part, firstDOMNode); } // set the last item of domNodes in parentNode setLastItemInParentDOM(parentNode, nodeInstance); } function handleTagEffect(fiber: Fiber) { const { nodeInstance, alternate, node } = fiber; // $FlowFixMe: TagNode will always be inside Array part of node part const part: ArrayPart | NodePart = fiber.part; const { parentNode } = part; const _isTagElement = isTagElementNode(node); // if it is the tag element handle the attributes from same fiber if (_isTagElement) { handleAttributeEffect(fiber, nodeInstance.domNodes[0]); } // if the alternate node is there rearrange the element if required, or else just add the new node if (alternate) { reArrangeExistingNode(fiber, alternate); } else { const previousSibling = getCorrectPreviousSibling(part); const nextSibling = getNextSibling(parentNode, previousSibling); const domNodes: Array = insertBefore(parentNode, nextSibling, nodeInstance.fragment); /** * when we add nodes first time * and we are rendering as fragment it means the fragment might have childNodes * which nodeInstance does not have, so for such cases we should reset nodeList on nodeInstance; */ if (!_isTagElement) { nodeInstance.domNodes = domNodes; } setFirstNodeReference(part, domNodes[0]); // set the last item of domNodes in parentNode setLastItemInParentDOM(parentNode, nodeInstance); } } function handleComponentEffect(fiber) { const { node, nodeInstance, root } = fiber; const { updateType } = root; const { nodeType } = node; const brahmosData = nodeInstance[BRAHMOS_DATA_KEY]; const isDeferredUpdate = updateType === UPDATE_TYPE_DEFERRED; if (nodeType === CLASS_COMPONENT_NODE) { // if it is deferredUpdate set the memoizedValues into nodeInstance state and prop if (isDeferredUpdate) { Object.assign(nodeInstance, brahmosData.memoizedValues); } const { props: prevProps, state: prevState } = brahmosData.committedValues; brahmosData.lastSnapshot = callLifeCycle(nodeInstance, 'getSnapshotBeforeUpdate', [ prevProps, prevState, ]); } else { // clean the existing effect cleanEffects(fiber, false); } // remove all the pending updates associated with current transition const { transitionId } = getTransitionFromFiber(fiber, null); const pendingUpdatesKey = getPendingUpdatesKey(updateType); brahmosData[pendingUpdatesKey] = isDeferredUpdate ? brahmosData[pendingUpdatesKey].filter((stateMeta) => stateMeta.transitionId !== transitionId) : []; // reset isDirty flag brahmosData.isDirty = false; brahmosData.renderCount = 0; root.postCommitEffects.push(fiber); } function handleComponentPostCommitEffect(fiber) { const { node, nodeInstance, root, childFiberError } = fiber; const { updateType } = root; const { nodeType, ref } = node; const brahmosData = nodeInstance[BRAHMOS_DATA_KEY]; if (nodeType === CLASS_COMPONENT_NODE) { const { props, state } = nodeInstance; const { committedValues, lastSnapshot } = brahmosData; // get the previous state and prevProps const { props: prevProps, state: prevState } = committedValues; /** * if it is first time rendered call componentDidMount or else call componentDidUpdate * prevProps will not be available for first time render */ if (!prevProps) { callLifeCycle(nodeInstance, 'componentDidMount'); } else { callLifeCycle(nodeInstance, 'componentDidUpdate', [prevProps, prevState, lastSnapshot]); } if (childFiberError) { callLifeCycle(nodeInstance, 'componentDidCatch', [ childFiberError.error, childFiberError.errorInfo, ]); // reset the error fiber.childFiberError = null; } // if the component node has ref call the ref with the node instance if (ref) setRef(ref, nodeInstance); // after commit is done set the current prop and state on committed values committedValues.props = props; committedValues.state = state; brahmosData.memoizedValues = null; } else { // call effects of functional component runEffects(fiber); // switch deferred hooks array and syncHooks hooks array, if it is deferred state update if (updateType === UPDATE_TYPE_DEFERRED) { const { syncHooks, deferredHooks } = nodeInstance; nodeInstance.deferredHooks = syncHooks; nodeInstance.syncHooks = deferredHooks; } } // mark component as mounted brahmosData.mounted = true; // add fiber reference on component instance, so the component is aware of its fiber brahmosData.fiber = fiber; } function handleAttributeEffect(fiber, domNode) { const { node, alternate, isSvgPart } = fiber; const { props, ref } = node; const oldProps = alternate && alternate.node.props; updateNodeAttributes(domNode, props, oldProps, isSvgPart); // set ref if present if (ref) setRef(ref, domNode); } export function resetEffectProperties(root: HostFiber) { root.tearDownFibers = []; root.postCommitEffects = []; root.hasUncommittedEffect = false; /** * reset retryFiber. A retryFiber might not get reset if * the sync render happened while the fiber processing is * scheduled */ root.retryFiber = null; // reset after render callbacks root.resetRenderCallbacks(); } /** * reset properties which are not required in future * of alternate fiber so those property values can be garbage collected */ function resetAlternate(alternate) { alternate.node = null; alternate.nodeInstance = null; alternate.child = null; alternate.sibling = null; } export function removeTransitionFromRoot(root: HostFiber): void { const { currentTransition, pendingTransitions } = root; const currentTransitionIndex = pendingTransitions.indexOf(currentTransition); if (currentTransitionIndex !== -1) { pendingTransitions.splice(currentTransitionIndex, 1); } } function handleFiberEffect(fiber) { const { node, alternate } = fiber; const _isComponentNode = node && isComponentNode(node); // if the fiber is part of an array and requires rearrange then do it if (_isComponentNode && alternate) { reArrangeExistingNode(fiber, alternate); } // if node has uncommitted effect, handle the effect if (fiber.hasUncommittedEffect === EFFECT_TYPE_OTHER) { if (isPrimitiveNode(node)) { updateTextNode(fiber); } else if (isTagNode(node)) { handleTagEffect(fiber); // TODO: Handle rearrange type of effect } else if (_isComponentNode) { handleComponentEffect(fiber); } else if (node.nodeType === ATTRIBUTE_NODE) { handleAttributeEffect(fiber, fiber.part.domNode); } // reset the hasUncommittedEffect flag fiber.hasUncommittedEffect = EFFECT_TYPE_NONE; } /** * once the fiber is committed, we can remove child and sibling link from alternate, * so unused child and sibling fiber (if not linked as alternate of any current node) * can be garbage collected */ if (alternate) { resetAlternate(alternate); } } /** * Fix pointers on fibers, and return the fibers with effects */ export function preCommitBookkeeping(root: HostFiber): Array { const { updateType, wip, current } = root; const updateTimeKey = getUpdateTimeKey(updateType); const lastCompleteTime = root[getLastCompleteTimeKey(updateType)]; const fibersWithEffect = []; let fiber = updateType === UPDATE_TYPE_SYNC ? current : wip; while (fiber) { const { createdAt, node, child, hasUncommittedEffect } = fiber; const updateTime = fiber[updateTimeKey]; const fiberIsNew = createdAt > lastCompleteTime; const hierarchyHasUpdates = hasUncommittedEffect || updateTime > lastCompleteTime; if (hasUncommittedEffect) { // push fiber in new fiber list fibersWithEffect.push(fiber); } // correct the references if (fiberIsNew) { /** * if child is there and it does not point back to correct parent * set the pointer back to parent. This can happen if the fiber is new * but the child is an existing fiber. This can happen when we haven't * processed fiber and just cloned from the current tree * We don't do this during rendering phase to not disturb the current tree */ if (child && child.parent !== fiber) child.parent = fiber; /** * If fiber is new and it is a component node we will need update the fiber * reference in the component node */ if (node && isComponentNode(node)) { fiber.nodeInstance[BRAHMOS_DATA_KEY].fiber = fiber; } } /** * do a depth first traversal, * go to child fiber only if the fiber is new, if its not * it means no child has updates */ if (child && hierarchyHasUpdates) fiber = child; else { while (fiber !== root && !fiber.sibling) fiber = fiber.parent; fiber = fiber.sibling; } } return fibersWithEffect; } export default function effectLoop(root: HostFiber, fibersWithEffect: Array): void { // loop on new fibers hand call if effect needs to be called for (let i = 0, ln = fibersWithEffect.length; i < ln; i++) { handleFiberEffect(fibersWithEffect[i]); } const { postCommitEffects } = root; // after applying the effects run all the post effects for (let i = postCommitEffects.length - 1; i >= 0; i--) { handleComponentPostCommitEffect(postCommitEffects[i]); } // remove the current transition from pending transition removeTransitionFromRoot(root); // once all effect has been processed update root's last effect node and postCommitEffects resetEffectProperties(root); // clear the force update node from root only after the effect root.forcedUpdateWith = null; } ================================================ FILE: src/fiber.js ================================================ // @flow import { isComponentNode, isPrimitiveNode, ATTRIBUTE_NODE, isTagElementNode, isHtmlTagNode, } from './brahmosNode'; import { UPDATE_TYPE_DEFERRED, BRAHMOS_DATA_KEY, EFFECT_TYPE_NONE, UPDATE_TYPE_SYNC, UPDATE_SOURCE_DEFAULT, } from './configs'; import { now, isNil } from './utils'; import type { Fiber, HostFiber, BrahmosNode, Part, UpdateType, EffectType, AnyComponentInstance, ExtendedElement, } from './flow.types'; let currentComponentFiber; export function setCurrentComponentFiber(fiber: ?Fiber) { currentComponentFiber = fiber; } export function getCurrentComponentFiber(): Fiber { // $FlowFixMe: Get current component is always called during render, where it will be present return currentComponentFiber; } export function getLastCompleteTimeKey( type: string, ): 'lastDeferredCompleteTime' | 'lastCompleteTime' { return type === UPDATE_TYPE_DEFERRED ? 'lastDeferredCompleteTime' : 'lastCompleteTime'; } export function getUpdateTimeKey(type: string): 'deferredUpdateTime' | 'updateTime' { return type === UPDATE_TYPE_DEFERRED ? 'deferredUpdateTime' : 'updateTime'; } export function setUpdateTime(fiber: Fiber, type: UpdateType) { const key = getUpdateTimeKey(type); const time = now(); while (fiber) { fiber[key] = time; fiber = fiber.parent; } } // link the new fiber to its parent or it's previous sibling function linkFiber(fiber, refFiber, parentFiber) { if (refFiber === parentFiber) { parentFiber.child = fiber; } else { refFiber.sibling = fiber; } fiber.parent = parentFiber; } // function to mark pending effects on the fiber and root export function markPendingEffect(fiber: Fiber, effectType: EffectType) { fiber.hasUncommittedEffect = effectType; fiber.root.hasUncommittedEffect = true; } export function cloneCurrentFiber( fiber: Fiber, wipFiber: ?Fiber, refFiber: Fiber, parentFiber: Fiber, ): Fiber { const { root, node, part, nodeInstance, child } = fiber; const updateTimeKey = getUpdateTimeKey(root.updateType); if (!wipFiber) { wipFiber = createFiber(root, node, part); // add fibers as each others alternate addAlternates(fiber, wipFiber); } else { wipFiber.node = node; wipFiber.part = part; /** * As the cloned node is treated as new fiber, reset the createdAt time */ wipFiber.createdAt = now(); } /** * When we are cloning a fiber we should prevent the fiber to tear down * A fiber can be marked for tearDown but after suspend (through suspense) / or error boundaries * it can be used again making the tear down stale. */ fiber.shouldTearDown = false; // add the nodeInstance to cloned fiber wipFiber.nodeInstance = nodeInstance; /** * Add the current child to wipFiber. * This is required so that new fiber is pointing to the existing child fiber * Note: We don't need to copy sibling as it will be set by loop, from where ever * the cloneMethod is called. * So make sure clone method is not called for only a single child if there multiple child. * */ wipFiber.child = child; /** * We should add update times from parent fiber. */ wipFiber[updateTimeKey] = parentFiber[updateTimeKey]; // link the new fiber to its parent or it's previous sibling linkFiber(wipFiber, refFiber, parentFiber); return wipFiber; } export function getNextChildFiber(refFiber: Fiber, parentFiber: Fiber): ?Fiber { return refFiber === parentFiber ? refFiber.child : refFiber.sibling; } export function cloneChildrenFibers(fiber: Fiber): void { let { child, root } = fiber; /** * No need to clone children if the updateType is sync, */ if (root.updateType === UPDATE_TYPE_SYNC) { return; } let lastChild; while (child) { /** * use the alternate node as wip node, as its no longer used by current node, * so we can reuse the object instead of creating a new one. * If it doesn't have any alternate node it means this is a new node, * so need to create them again */ const { alternate } = child; lastChild = cloneCurrentFiber(child, alternate, lastChild || fiber, fiber); child = child.sibling; } } export function createHostFiber(domNode: ExtendedElement): HostFiber { let afterRenderCallbacks = []; return { updateType: UPDATE_TYPE_DEFERRED, updateSource: UPDATE_SOURCE_DEFAULT, cancelSchedule: null, domNode, forcedUpdateWith: null, // $FlowFixMe: Current will be set as soon after we create host fiber current: null, wip: null, child: null, retryFiber: null, currentTransition: null, hasUncommittedEffect: false, pendingTransitions: [], tearDownFibers: [], postCommitEffects: [], batchUpdates: {}, lastDeferredCompleteTime: 0, lastCompleteTime: 0, deferredUpdateTime: 0, updateTime: 0, /** After render utils */ afterRender(cb) { // if the callback is not already added add the callback if (!afterRenderCallbacks.includes(cb)) { afterRenderCallbacks.push(cb); } }, callRenderCallbacks() { for (let i = 0, ln = afterRenderCallbacks.length; i < ln; i++) { afterRenderCallbacks[i](); } }, resetRenderCallbacks() { afterRenderCallbacks = []; }, }; } export function createFiber(root: HostFiber, node: BrahmosNode, part: Part): Fiber { // if a node is ported node, update the part information if (node && node.portalContainer) { // $FlowFixMe: If node has portal container, the fiber part has to be ArrayPart or NodePart part.parentNode = node.portalContainer; } return { node, nodeInstance: null, root, // $FlowFixMe: We always link fiber with its parent, so after creating parent will always be set parent: null, child: null, sibling: null, part, alternate: null, // points to the current fiber context: null, // Points to the context applicable for that fiber childFiberError: null, isSvgPart: false, deferredUpdateTime: 0, updateTime: 0, processedTime: 0, // processedTime 0 signifies it needs processing createdAt: now(), shouldTearDown: false, hasUncommittedEffect: EFFECT_TYPE_NONE, }; } /** * add to fibers as alternate to each other */ export function addAlternates(current: Fiber, wip: Fiber): void { if (current) { current.alternate = wip; } wip.alternate = current; } export function createAndLink( node: any, part: Part, currentFiber: ?Fiber, refFiber: Fiber, parentFiber: Fiber, ): Fiber { const { root } = refFiber; const updateTimeKey = getUpdateTimeKey(root.updateType); let fiber; if ( currentFiber && !isNil(currentFiber.node) && !isNil(node) && shouldClone(node, currentFiber.node) ) { fiber = cloneCurrentFiber(currentFiber, currentFiber.alternate, refFiber, parentFiber); // assign new node and part to the fiber fiber.node = node; fiber.part = part; } else { fiber = createFiber(root, node, part); // if current fiber is there mark it to tear down if (currentFiber) { markToTearDown(currentFiber); } } linkFiber(fiber, refFiber, parentFiber); fiber.processedTime = 0; // add parent's inheriting property to children fiber[updateTimeKey] = parentFiber[updateTimeKey]; fiber.context = parentFiber.context; fiber.isSvgPart = parentFiber.isSvgPart; return fiber; } function shouldClone(newNode: any, oldNode: any): boolean { return ( // if it is primitive node and old node is also primitive we can clone the previous fiber (isPrimitiveNode(newNode) && isPrimitiveNode(oldNode)) || /** * if the new node is attribute node, no need to check for old node as the * if there is oldNode it will always be same type in case of attribute node */ newNode.nodeType === ATTRIBUTE_NODE || // if both are array type than clone (Array.isArray(newNode) && Array.isArray(oldNode)) || // if it is component node or tag element node and node type matches with oldNode's type we should clone the current ((isComponentNode(newNode) || isTagElementNode(newNode)) && newNode.type === oldNode.type) || // if it is tag node and node's template matches with oldNode's template we should clone the current (isHtmlTagNode(newNode) && newNode.template === oldNode.template) ); } function needProcessing(fiber, lastCompleteTime, updateTimeKey) { return fiber && fiber[updateTimeKey] >= lastCompleteTime; } function getFiberWhichRequiresProcessing(fiber, lastCompleteTime, updateTimeKey) { if (!fiber) return; // keep looping till we find a child which needs processing while (fiber && !needProcessing(fiber, lastCompleteTime, updateTimeKey)) fiber = fiber.sibling; return fiber; } export function getNextFiber( fiber: Fiber, topFiber: Fiber | HostFiber, lastCompleteTime: number, updateTimeKey: string, ): Fiber { /** * Skip fibers which does not require processing */ // if there is a child which required processing return that child const child = getFiberWhichRequiresProcessing(fiber.child, lastCompleteTime, updateTimeKey); if (child) return child; let sibling; // or else return the sibling or the next uncle which requires processing while ( !(sibling = getFiberWhichRequiresProcessing(fiber.sibling, lastCompleteTime, updateTimeKey)) ) { // go to fiber parents fiber = fiber.parent; // if the parent fiber is topFiber, no further processing is required, so return that if (fiber === topFiber) return fiber; } // return fiber's sibling return sibling; } /** Function to get fiber from the component */ export function getFiberFromComponent(component: AnyComponentInstance): Fiber { /** * $FlowFixMe: Component will always fiber once its rendered, so we can assume fiber is not null here * And this method is always called after component is mounted once */ return component[BRAHMOS_DATA_KEY].fiber; } /** * Function to reset child to committed fiber. * This is a usual case when we interrupt workLoop, the fiber might be pointing to * the wrong uncommitted fiber, in which case we reset it to the alternate * (which points to the committed one) */ export function resetToCommittedChild(fiber: Fiber): void { const { root, child } = fiber; /** * if the child fiber is created but not committed yet, * reset the child fiber to alternate child */ if (child && child.createdAt > root.lastCompleteTime) { fiber.child = child.alternate; } } /** * function to push a fiber for tearDown */ export function markToTearDown(fiber: Fiber): void { fiber.shouldTearDown = true; fiber.root.tearDownFibers.push(fiber); } ================================================ FILE: src/flow.types.js ================================================ // @flow import typeof { LAST_ARRAY_DOM_KEY, ROOT_FIBER_KEY, BRAHMOS_DATA_KEY, UPDATE_TYPE_SYNC, UPDATE_TYPE_DEFERRED, UPDATE_SOURCE_DEFAULT, UPDATE_SOURCE_IMMEDIATE_ACTION, UPDATE_SOURCE_TRANSITION, TRANSITION_STATE_INITIAL, TRANSITION_STATE_START, TRANSITION_STATE_SUSPENDED, TRANSITION_STATE_RESOLVED, TRANSITION_STATE_COMPLETED, TRANSITION_STATE_TIMED_OUT, EFFECT_TYPE_NONE, EFFECT_TYPE_PLACEMENT, EFFECT_TYPE_OTHER, } from './configs'; /** Extend dom type */ export type ExtendedElement = Element & { [key: LAST_ARRAY_DOM_KEY | ROOT_FIBER_KEY]: any, }; /** Utility types */ export type ObjectLiteral = { [key: string]: any }; export type ArrayCallback = (child: any, index: number, array: Array) => any; /** Effect type */ export type EffectType = EFFECT_TYPE_NONE | EFFECT_TYPE_PLACEMENT | EFFECT_TYPE_OTHER; /** Error Type */ export type ErrorInfo = { componentStack: string }; /** Update types */ export type UpdateType = UPDATE_TYPE_SYNC | UPDATE_TYPE_DEFERRED; export type UpdateSource = | UPDATE_SOURCE_DEFAULT | UPDATE_SOURCE_IMMEDIATE_ACTION | UPDATE_SOURCE_TRANSITION; /** Update types end */ /** Part types */ export type PartMeta = {| tagAttrs?: Array, attrIndex: number, isAttribute: boolean, refNodeIndex: number, prevChildIndex: number, hasExpressionSibling: boolean, |}; export type AttributePart = {| isAttribute: true, domNode: ExtendedElement, tagAttrs: Array, attrIndex: number, |}; export type NodePart = {| isNode: boolean, parentNode: ExtendedElement, previousSibling: ?Node, |}; export type ArrayPart = {| isArrayNode: boolean, parentNode: ExtendedElement, previousSibling: ?Node, nodeIndex: number, firstDOMNode?: Node, parentArrayPart?: ArrayPart, |}; export type Part = AttributePart | NodePart | ArrayPart; /** Part types end */ /** Transition types */ export type TransitionState = | TRANSITION_STATE_INITIAL | TRANSITION_STATE_START | TRANSITION_STATE_SUSPENDED | TRANSITION_STATE_RESOLVED | TRANSITION_STATE_COMPLETED | TRANSITION_STATE_TIMED_OUT; export type PredefinedTransition = {| transitionId: string, tryCount: number, transitionState: TRANSITION_STATE_TIMED_OUT, |}; export type Transition = {| transitionId: string, tryCount: number, transitionTimeout: ?TimeoutID, transitionState: TransitionState, isPending: boolean, // eslint-disable-next-line no-use-before-define pendingSuspense: Array, clearTimeout: () => void, updatePendingState: (isPending: boolean, updateSource: UpdateSource) => void, startTransition: (cb: Function) => void, |}; export type AnyTransition = Transition | PredefinedTransition; /** Transition types end */ /** Refs type */ export type ObjectRef = { current: any }; export type FunctionalRef = (ref: any) => void; export type Ref = ObjectRef | FunctionalRef; /** Template interface */ export interface TemplateTagType { [key: 'svgTemplate' | 'template']: ?HTMLTemplateElement; template: ?HTMLTemplateElement; svgTemplate: ?HTMLTemplateElement; strings: Array; partsMeta: Array; partMetaCode: string; staticTree: any; +create: (isSvgPart: boolean) => void; } export interface TemplateNodeType { templateResult: TemplateTagType; fragment: DocumentFragment; parts: Array; domNodes: Array; patched: boolean; +patchParts: (nodePart: NodePart) => void; } export interface TagNodeType { fragment: Array; parts: Array; domNodes: Array; } /** Template interface end */ /** Brahmos Node type */ export type BrahmosNode = {| $$typeof: symbol, nodeType: ?symbol, key?: number | string, ref: ?Ref, portalContainer: ?ExtendedElement, type: Function, props: ?ObjectLiteral, values: ?Array, template: ?TemplateTagType, |}; /** Brahmos Node type end */ /** Fiber types */ export type Fiber = {| node: any, nodeInstance: any, // eslint-disable-next-line no-use-before-define root: HostFiber, parent: Fiber, child: ?Fiber, sibling: ?Fiber, part: any, alternate: ?Fiber, // eslint-disable-next-line no-use-before-define context: ?AllContext, childFiberError: ?{ error: Error, errorInfo: ErrorInfo }, isSvgPart: boolean, deferredUpdateTime: number, updateTime: number, processedTime: number, createdAt: number, shouldTearDown: boolean, hasUncommittedEffect: EffectType, |}; export type HostFiber = {| updateType: UpdateType, updateSource: UpdateSource, cancelSchedule: Function, domNode: ExtendedElement, forcedUpdateWith: ?Fiber, current: Fiber, wip: ?Fiber, child: ?Fiber, retryFiber: ?Fiber, currentTransition: ?AnyTransition, hasUncommittedEffect: boolean, pendingTransitions: Array, tearDownFibers: Array, postCommitEffects: Array, batchUpdates: { [key: string]: number }, lastDeferredCompleteTime: number, lastCompleteTime: number, deferredUpdateTime: number, updateTime: number, afterRender: (cb: Function) => void, callRenderCallbacks: () => void, resetRenderCallbacks: () => void, |}; /** Fiber types end */ /** NodeInstance Interfaces */ export type ComponentBrahmosData = {| pendingSyncUpdates: Array, pendingDeferredUpdates: Array, fiber: ?Fiber, nodes: any, isDirty: boolean, mounted: boolean, renderCount: number, |}; type CommittedValues = { props: ObjectLiteral, state: ?ObjectLiteral, }; type MemoizedValues = ?{ props: ObjectLiteral, state: ?ObjectLiteral, transitionId: string, }; export type ClassComponentBrahmosData = {| ...ComponentBrahmosData, committedValues: CommittedValues, memoizedValues: MemoizedValues, |}; export type NewState = Object | ((state: ?ObjectLiteral) => Object); export type StateCallback = (state: ?ObjectLiteral) => void; export interface ComponentInstance { props: ObjectLiteral; state: ?ObjectLiteral; context: any; [key: BRAHMOS_DATA_KEY]: ClassComponentBrahmosData; +setState: (newState: NewState, callback: StateCallback) => void; +forceUpdate: (callback: () => void) => void; componentDidMount?: () => void; shouldComponentUpdate?: (nextProps: ObjectLiteral, nextState: ?ObjectLiteral) => boolean; getSnapshotBeforeUpdate?: (prevProps: ObjectLiteral, prevState: ?ObjectLiteral) => void; componentDidUpdate?: (prevProps: ObjectLiteral, prevState: ?ObjectLiteral, snapshot: any) => void; componentDidCatch?: (error: Error, info: ErrorInfo) => void; componentWillUnmount?: () => void; +render: () => any; +__render: () => any; } export interface PureComponentInstance extends ComponentInstance { isPureReactComponent: boolean; } export type SuspenseProps = { fallback: any, }; export type SuspenseListProps = { revealOrder: 'forwards' | 'backwards' | 'together', tail: 'collapsed' | 'hidden', }; export interface SuspenseInstance extends ComponentInstance { props: SuspenseProps; suspenseManagers: ObjectLiteral; +handleSuspender: (Promise, Fiber) => void; } export interface SuspenseListInstance extends ComponentInstance { props: SuspenseListProps; suspenseManagers: ObjectLiteral; } export interface ProviderInstance extends ComponentInstance { subs: Array<(providerValue: any) => void>; // eslint-disable-next-line no-use-before-define sub: (componentInstance: ComponentClassInstance) => void; } export type FunctionalComponentInstance = { syncHooks: Array, deferredHooks: Array, pointer: number, context: any, __render: (props: ObjectLiteral) => any, [key: BRAHMOS_DATA_KEY]: ComponentBrahmosData, }; export type ComponentClassInstance = | ComponentInstance | PureComponentInstance | SuspenseInstance | SuspenseListInstance; export type AnyComponentInstance = ComponentClassInstance | FunctionalComponentInstance; export type FunctionalComponent = { (props: ObjectLiteral, ref: ?Ref): any, displayName: ?string, __loadLazyComponent: ?() => void, }; export type ClassComponent = { getDerivedStateFromProps: (props: ObjectLiteral, state: ?ObjectLiteral) => ObjectLiteral | null, getDerivedStateFromError: (error: Error) => ObjectLiteral | null, displayName: ?string, name: string, prototype: ComponentClassInstance, }; export type ProviderClassType = ClassComponent & { __ccId: string, }; export type NodeInstance = AnyComponentInstance | TemplateNodeType; /** NodeInstance Interfaces end */ /** Context type */ export type ContextType = { id: string, defaultValue: any, Provider: ProviderClassType, Consumer: ClassComponent, }; export type AllContext = { [key: string]: ProviderInstance }; /** Update types */ export type ClassComponentUpdate = { transitionId: string, state: NewState, callback: StateCallback, }; export type FunctionalComponentUpdate = { transitionId: string, updater: () => void, }; export type PendingUpdates = Array | Array; ================================================ FILE: src/functionalComponentInstance.js ================================================ // @flow import { prepareHooksForRender } from './hooks'; import { BRAHMOS_DATA_KEY } from './configs'; import type { FunctionalComponentInstance, ObjectLiteral } from './flow.types'; export default function functionalComponentInstance( FuncComponent: Function, ): FunctionalComponentInstance { return { syncHooks: [], deferredHooks: [], context: undefined, pointer: 0, __render(props: ObjectLiteral) { prepareHooksForRender(); const nodes = FuncComponent(props); this[BRAHMOS_DATA_KEY].nodes = nodes; return nodes; }, // keep the dynamic attributes on last so it's transpiled in compact way [BRAHMOS_DATA_KEY]: { pendingSyncUpdates: [], pendingDeferredUpdates: [], fiber: null, nodes: null, isDirty: false, mounted: false, renderCount: 0, }, }; } ================================================ FILE: src/helpers/shallowEqual.js ================================================ // @flow /** * Source : https://github.com/facebook/react/blob/master/packages/shared/shallowEqual.js */ function shallowEqual(objA: any, objB: any): boolean { if (Object.is(objA, objB)) { return true; } if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) { return false; } const keysA = Object.keys(objA); const keysB = Object.keys(objB); if (keysA.length !== keysB.length) { return false; } // Test for A's keys different from B. for (let i = 0; i < keysA.length; i++) { if (!hasOwnProperty.call(objB, keysA[i]) || !Object.is(objA[keysA[i]], objB[keysA[i]])) { return false; } } return true; } export default shallowEqual; ================================================ FILE: src/hooks.js ================================================ // @flow import reRender from './reRender'; import { getConsumerCallback } from './createContext'; import { getUniqueId, timestamp } from './utils'; import { UPDATE_TYPE_SYNC, UPDATE_TYPE_DEFERRED, UPDATE_SOURCE_TRANSITION, BRAHMOS_DATA_KEY, UPDATE_SOURCE_IMMEDIATE_ACTION, TRANSITION_STATE_INITIAL, TRANSITION_STATE_START, TRANSITION_STATE_TIMED_OUT, } from './configs'; import { getCurrentUpdateSource, withUpdateSource, withTransition, getPendingUpdates, getUpdateType, guardedSetState, } from './updateUtils'; import { getFiberFromComponent, getCurrentComponentFiber } from './fiber'; import type { Fiber, Transition, ObjectRef, StateCallback, ContextType, FunctionalComponentUpdate, } from './flow.types'; type DeferredValueHookOptions = { timeoutMs: number, }; type TransitionOptions = { timeoutMs: number, }; type StateHookResult = [any, (state: any) => any]; type UseTransitionResult = [(cb: Function) => void, boolean]; function getCurrentComponent() { return getCurrentComponentFiber().nodeInstance; } /** * clone hooks, syncHooks to deferredHooks */ function cloneHooks(component) { const { renderCount } = component[BRAHMOS_DATA_KEY]; component.deferredHooks = component.syncHooks.map((hook, index) => { if (Array.isArray(hook)) { return [...hook]; } else if (hook.transitionId) { /** * Transition hooks are shared across sync and deferred hooks, * so use the same instance of hook don't clone it */ return hook; // eslint-disable-next-line no-prototype-builtins } else if (hook.hasOwnProperty('current') && renderCount > 1) { /** * In case of useRef we need to retain the the reference if there if the * render is getting called multiple times in one render cycle */ return component.deferredHooks[index] || hook; } return { ...hook }; }); } /** * Get the current hooks array based on updateType */ function getHooksList(updateType, component) { const { syncHooks, deferredHooks } = component; return updateType === UPDATE_TYPE_SYNC ? syncHooks : deferredHooks; } /** * Get the current hooks array from the fiber */ function getHooksListFromFiber(fiber) { const { nodeInstance, root: { updateType }, } = fiber; return getHooksList(updateType, nodeInstance); } /** * Get current hook, based on the type of update we are doing * If it is inside transition of deferred update we need deferredHooksList, * or else we need the sync hooks list */ function getCurrentHook(updateType, hookIndex, component) { /** * if deferred hooks is not populated clone from the syncHooks * This will happen if the component has never been rendered in deferred mode. */ if (updateType === UPDATE_TYPE_DEFERRED && !component.deferredHooks.length) { cloneHooks(component); } const hooks = getHooksList(updateType, component); return hooks[hookIndex]; } /** * Method to check if two dependency array are same */ function isDependenciesChanged(deps, oldDeps) { // if oldDeps or deps are not defined consider it is changed every time if (!deps || !oldDeps || deps.length !== oldDeps.length) return true; for (let i = 0, ln = deps.length; i < ln; i++) { if (deps[i] !== oldDeps[i]) return true; } return false; } /** * Function to rerender component if state is changed */ function reRenderComponentIfRequired(component, state, lastState) { /** * check if state are different before rerendering, for seState triggered by event * we should always reRerender as event can have some side effects which are controlled */ if (getCurrentUpdateSource() === UPDATE_SOURCE_IMMEDIATE_ACTION || !Object.is(state, lastState)) { reRender(component); } } /** * A base method to return hook at specific pointer, * and if not available create a new pane * We also pass a method to get value from the hook which is passed to the component * Plus a method to check if hook has to be updated * * H: Hook, R: Hook result */ function defaultShouldUpdate(hook: H): boolean { return false; } function defaultReduce(hook: H): H | R { return hook; } function getHook<+H, R>( createHook: () => H, shouldUpdate: (hook: H) => boolean, reduce: (hook: H) => R, ): R { const fiber = getCurrentComponentFiber(); const { nodeInstance: component } = fiber; const { pointer } = component; const hooks = getHooksListFromFiber(fiber); let hook = hooks[pointer]; // if hook is not there initialize and add it to the pointer if (!hook || shouldUpdate(hook)) { hook = createHook(); hooks[pointer] = hook; } // increment the hook pointer component.pointer += 1; return reduce(hook); } export function prepareHooksForRender() { const fiber = getCurrentComponentFiber(); const { nodeInstance: component, root: { updateType }, } = fiber; component.pointer = 0; // based on update type clone the hooks to deferred hooks if (updateType === UPDATE_TYPE_DEFERRED) { cloneHooks(component); } // call all the pending update before trying to render, const pendingUpdates = ((getPendingUpdates(fiber): any): Array); pendingUpdates.forEach((task) => task.updater()); } /** * Base logic for state hooks */ function useStateBase( initialState: any, getNewState: (state: any, lastState: any) => any, ): StateHookResult { const component = getCurrentComponent(); const { pointer: hookIndex } = component; return getHook( (): StateHookResult => { /** * create a state hook */ if (typeof initialState === 'function') initialState = initialState(); const hook = [ initialState, (param: any): void => { const updateType = getUpdateType(); // get committed lastState, which will be up to date in sync hook list const currentHook = getCurrentHook(UPDATE_TYPE_SYNC, hookIndex, component); const lastState = currentHook[0]; const state = getNewState(param, lastState); const shouldRerender = guardedSetState(component, (transitionId) => ({ transitionId, updater() { /** * get the hook again inside, as the reference of currentHook might change * if we clone sync hook to deferred hook */ const stateHook = getCurrentHook(updateType, hookIndex, component); // call getNewState again as currentHook[0] might change if there are multiple setState stateHook[0] = getNewState(param, currentHook[0]); }, })); if (shouldRerender) reRenderComponentIfRequired(component, state, lastState); }, ]; return hook; }, defaultShouldUpdate, defaultReduce, ); } /** * Use state hook */ export function useState(initialState: any): [any, StateCallback] { return useStateBase(initialState, (state, lastState) => { if (typeof state === 'function') state = state(lastState); return state; }); } /** * Use ref hook */ export function useRef(initialValue: any): ObjectRef { return getHook( (): ObjectRef => { /** * create a ref hook */ return { current: initialValue, }; }, defaultShouldUpdate, defaultReduce, ); } /** * Use reducer hook */ export function useReducer( reducer: (state: any, action: any) => any, initialState: any, getInitialState: (initialState: any) => any, ): StateHookResult { /** * If getInitialState method is provided, use that to form correct initial state * Or else use passed initialState */ const _initialState = getInitialState ? () => getInitialState(initialState) : initialState; return useStateBase(_initialState, (action, lastState) => { const state = reducer(lastState, action); return state; }); } /** * use memo hook */ export function useMemo(create: () => any, dependencies: Array): any { const createHook = () => { return { value: create(), dependencies, }; }; const shouldUpdate = (hook) => isDependenciesChanged(dependencies, hook.dependencies); const reduce = (hook) => hook.value; return getHook(createHook, shouldUpdate, reduce); } /** * Use callback hook */ export function useCallback(callback: Function, dependencies: Array): Function { return useMemo(() => callback, dependencies); } /** * Base module to create effect hooks */ function useEffectBase(effectHandler, dependencies) { const fiber = getCurrentComponentFiber(); const { nodeInstance: component } = fiber; const { pointer } = component; const hooks = getHooksListFromFiber(fiber); const lastHook = hooks[pointer] || { animationFrame: null, cleanEffect: null, }; const hook = { ...lastHook, isDependenciesChanged: isDependenciesChanged(dependencies, lastHook.dependencies), dependencies, effect() { // if dependency is changed then only call the the effect handler if (hook.isDependenciesChanged) { effectHandler(hook); } }, }; hooks[pointer] = hook; component.pointer += 1; } /** * Use effect hook */ export function useEffect(callback: () => ?Function, dependencies: Array): void { useEffectBase((hook) => { /** * Run effect asynchronously after the paint cycle is finished */ // cancel the previous callback if not yet executed cancelAnimationFrame(hook.animationFrame); // run affect after next paint hook.animationFrame = requestAnimationFrame(() => { setTimeout(() => { hook.cleanEffect = callback(); }); }); }, dependencies); } export function useLayoutEffect(callback: () => ?Function, dependencies: Array): void { useEffectBase((hook) => { // run effect synchronously hook.cleanEffect = callback(); }, dependencies); } /** * useDebugValue hook. For now this is just a placeholder, * As there is no devtool support it. Revisit it when devtool is supported */ export function useDebugValue() { // This is just a placeholder for react compatibility } /** * Create context hook */ export function useContext(Context: ContextType): any { const { nodeInstance: component, context } = getCurrentComponentFiber(); const { id, defaultValue } = Context; /** * $FlowFixMe: Context will always be present in component fiber * We have kept it optional for fiber as we don't want to create new object for each fiber */ const provider = context[id]; const value = provider ? provider.props.value : defaultValue; useLayoutEffect(() => { // subscribe to provider for the context value change if (provider) { const { subs } = provider; const callback = getConsumerCallback(component); subs.push(callback); return () => { subs.splice(subs.indexOf(callback), 1); }; } }, []); // store the context value in current component so we can check if value is changed on subscribed callback component.context = value; return value; } /** * Transition hook */ export function useTransition({ timeoutMs }: TransitionOptions): UseTransitionResult { const component = getCurrentComponent(); return getHook( () => { /** * create a transition hook */ const hook: Transition = { transitionId: getUniqueId(), tryCount: 0, isPending: false, transitionTimeout: null, pendingSuspense: [], transitionState: TRANSITION_STATE_INITIAL, clearTimeout() { clearTimeout(hook.transitionTimeout); }, updatePendingState(isPending, updateSource) { hook.isPending = isPending; // mark component to force update as isPending is not treated as state change component[BRAHMOS_DATA_KEY].isDirty = true; const reRenderCb = () => { reRender(component); }; if (updateSource === UPDATE_SOURCE_TRANSITION) { withTransition(hook, reRenderCb); } else { withUpdateSource(updateSource, reRenderCb); } }, startTransition(cb: Function) { const initialUpdateSource = getCurrentUpdateSource(); const { root } = getFiberFromComponent(component); // reset the transitionState and pending suspense hook.transitionState = TRANSITION_STATE_START; hook.pendingSuspense = []; // clear pending timeout hook.clearTimeout(); // set the transitionId globally so that state updates can get the transition id withTransition(hook, cb); /** * If cb does not have any setState, we don't have to unnecessary * set isPending flag, transitionState and trigger reRender. */ if (root.lastDeferredCompleteTime < root.deferredUpdateTime) { hook.updatePendingState(true, initialUpdateSource); } /** * Set a timeout which set's the is pending to false and then triggers a deferred update */ hook.transitionTimeout = setTimeout(() => { hook.transitionState = TRANSITION_STATE_TIMED_OUT; hook.updatePendingState(false, UPDATE_SOURCE_TRANSITION); }, timeoutMs); }, }; return hook; }, defaultShouldUpdate, ({ startTransition, isPending }: Transition): UseTransitionResult => [ startTransition, isPending, ], ); } /** * A hook to have deferred value */ export function useDeferredValue(value: any, { timeoutMs }: DeferredValueHookOptions): any { const [startTransition] = useTransition({ timeoutMs }); const [deferredValue, setDeferredValue] = useState(value); const timeStampRef = useRef(0); /** * If there is a timestamp that denotes the timestamp form where the data * went stale, timestamp 0 means the data is not stale. */ const { current: staleTime } = timeStampRef; const currentTime = timestamp(); if (value === deferredValue) { // if value is not stale reset timestamp timeStampRef.current = 0; } else if (staleTime === 0) { // if the value just got stale mark the stale time timeStampRef.current = currentTime; } else if (currentTime > staleTime + timeoutMs) { // when ever the stale data times out update the deferred value timeStampRef.current = 0; setDeferredValue(value); } useEffect(() => { startTransition(() => { setDeferredValue(value); }); }, [value]); return deferredValue; } /** * Method to run all the effects of a component */ export function runEffects(fiber: Fiber) { const hooks = getHooksListFromFiber(fiber); for (let i = 0, ln = hooks.length; i < ln; i++) { const hook = hooks[i]; if (hook.effect) { hook.effect(); } } } /** * Method to run cleanup all the effects of a component */ export function cleanEffects(fiber: Fiber, unmount: boolean): void { const hooks = getHooksListFromFiber(fiber); for (let i = 0, ln = hooks.length; i < ln; i++) { const hook = hooks[i]; if (hook.cleanEffect && (hook.isDependenciesChanged || unmount)) { hook.cleanEffect(); } // clear any pending transitions on unmount if (hook.clearTimeout && unmount) { hook.clearTimeout(); } } } ================================================ FILE: src/index.js ================================================ import * as Brahmos from './brahmos'; export * from './brahmos'; export default Brahmos; ================================================ FILE: src/memo.js ================================================ import { PureComponent } from './Component'; import { createBrahmosNode } from './circularDep'; export default function memo(Component, comparator) { class MemoizedComponent extends PureComponent { render() { return createBrahmosNode(Component, this.props); } } MemoizedComponent.displayName = `Memo(${Component.displayName || Component.name})`; return MemoizedComponent; } ================================================ FILE: src/parseChildren.js ================================================ // @flow import { TAG_ELEMENT_NODE } from './brahmosNode'; import { REACT_ELEMENT } from './configs'; import type { BrahmosNode, TemplateTagType } from './flow.types'; const TAG_REGEX = /<([^\s"'=<>/]+)/g; const ATTR_REGEX = /\s*([^\s"'=<>/]*)/g; const DOUBLE_QUOTE_REGEX = /[^"]*/g; const SINGLE_QUOTE_REGEX = /[^"]*/g; const TAG_END_REGEX = /<\/([^\s"'=<>]+)>/g; const STRING_CHILDREN_REGEX = /[^<]*/g; const COMMENT_REGEX = //g; const SELF_CLOSING_TAGS = [ 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr', ]; /** * Convert static string to dynamic nodes */ function parseStatic(strings: Array) { const elmStack = []; let insideTagOpenElem = false; let currentElm; const rootProps = {}; const addToProps = (props, child) => { if (!props.children) { // if there isn't a children, child becomes the only children props.children = child; } else if (Array.isArray(props.children)) { props.children.push(child); } else { // if node has single children create a array of child props.children = [props.children, child]; } }; const pushToParent = (child) => { const parentElm = elmStack[elmStack.length - 1]; // if there is any element in stack use that as parent or else add on root. addToProps(parentElm ? parentElm.props : rootProps, child); }; /** * Method to handle the children on close of opening element */ const pushChildren = () => { insideTagOpenElem = false; pushToParent(currentElm); /** * If the element is not self closing tag, add it in stack * We don't need to do it for self closing tags because they don't have any child */ if (!SELF_CLOSING_TAGS.includes(currentElm.type)) { elmStack.push(currentElm); } }; /** Function to add a dynamic part information, * so dynamic parts can be applied on the static tree * */ const pushDynamicPart = (index) => { const dynamicInfo = { $$BrahmosDynamicPart: index, }; // if we are inside tag opening element, we need to add dynamic part in attribute otherwise as child if (!currentElm) { addToProps(rootProps, dynamicInfo); } else if (insideTagOpenElem) { currentElm.props[`$$BrahmosDynamicPart${index}`] = dynamicInfo; } else { addToProps(currentElm.props, dynamicInfo); } }; strings.forEach((str, index) => { const ln = str.length; let tag; let i = 0; const regexSearch = (regex) => { // start searching from last index regex.lastIndex = i; const result = regex.exec(str); // if we find a result set the index to regex last index if (result) { i = regex.lastIndex; } return result; }; const getAttributeValue = (quoteRegex) => { /** * We need to start searching from i + 2 to accommodate =' or =" */ quoteRegex.lastIndex = i + 2; const result = quoteRegex.exec(str); // after successful search of value we need to set the i to lastIndex + 1 to accommodate ' or " i = quoteRegex.lastIndex + 1; /** * $FlowFixMe: We are running on a valid transform template, * so the regex will always have value on this logic */ return result[0]; }; while (i < ln) { if (str[i] === '<' && str[i + 1] === '/') { /** * when we see */ const result = regexSearch(TAG_REGEX); // $FlowFixMe: the result will be always be there here as the JSX transformation will have valid html tag = result[1]; currentElm = { $$typeof: REACT_ELEMENT, type: tag, nodeType: TAG_ELEMENT_NODE, props: {}, }; insideTagOpenElem = true; continue; } else if (str[i] === ' ' && insideTagOpenElem) { // If we encounter an empty space there is chance that we may get attribute next ATTR_REGEX.lastIndex = i; const result = regexSearch(ATTR_REGEX); let attrName; let attrValue; /** * If we got an attribute, get the attribute value and * associate the attribute name and value to currentElement */ if (result) { attrName = result[1]; if (str[i] !== '=') { // if there is no = followed by attribute name, it means its a boolean attribute with value true attrValue = true; } else if (str[i + 1] === `"`) { // if attribute name is followed by =" find the value between double quote attrValue = getAttributeValue(DOUBLE_QUOTE_REGEX); } else if (str[i + 1] === `'`) { // if attribute name is followed by =' find the value between single quote attrValue = getAttributeValue(SINGLE_QUOTE_REGEX); } if (attrName) { currentElm.props[attrName] = attrValue; } continue; } } else if (str[i] === '>' && insideTagOpenElem) { /** * If we encounter > and are inside tag opening element, * we are done with attributes, and we have start procession the children now * We also add the currentElement on stack so children have information about the parent hierarchy. */ pushChildren(); } else if (!insideTagOpenElem) { const result = regexSearch(STRING_CHILDREN_REGEX); // $FlowFixMe: regex search will always return something either empty string or some text data pushToParent(result[0]); continue; } i++; } pushDynamicPart(index); }); return rootProps.children; } const { hasOwnProperty } = Object.prototype; /** * function to merge dynamic values in static tree. * It creates clones and replaces placeholders by dynamic values */ function apply(tree: any, values) { if (tree == null || typeof tree !== 'object') { return tree; } let newTree = new tree.constructor(); // for objects/arrays create clone for (var key in tree) { if (hasOwnProperty.call(tree, key)) { const node = tree[key]; const valueIndex = node && node.$$BrahmosDynamicPart; /** * If we get a dynamic place holder check if its an attribute * If its an attribute we want to merge the dynamic part with other attributes, * or else we replace the placeholder by dynamic value */ if (valueIndex !== undefined) { const value = values[valueIndex]; if (key[0] === '$') { newTree = Object.assign(newTree, value); } else { newTree[key] = value; } } else { newTree[key] = apply(tree[key], values); } } } return newTree; } export default function parseChildren(node: BrahmosNode): BrahmosNode { // $FlowFixMe: Only template nodes are passed in this function const { values, template }: { values: Array, template: TemplateTagType } = node; const { strings } = template; if (!template.staticTree) { template.staticTree = parseStatic(strings); } return apply(template.staticTree, values); } ================================================ FILE: src/processArrayFiber.js ================================================ // @flow import { getNextChildFiber, createAndLink, markToTearDown, markPendingEffect } from './fiber'; import { getKey } from './brahmosNode'; import { EFFECT_TYPE_PLACEMENT, EFFECT_TYPE_OTHER } from './configs'; import type { Fiber } from './flow.types'; // handle array nodes export default function processArrayFiber(fiber: Fiber): void { const { node: nodes, part } = fiber; let refFiber = fiber; // $FlowFixMe: part will always be node part on array fiber const { parentNode, previousSibling, firstDOMNode } = part; const childKeyMap = new Map(); let i = 0; let childFiber = fiber; /** * check if getNextChildFiber(childFiber, fiber) return a child if yes then add it to map */ while ((childFiber = getNextChildFiber(childFiber, fiber))) { const key = getKey(childFiber.node, i); childKeyMap.set(key, childFiber); i++; } // /** * reset the previous children as we will link new children on fiber * This will make sure if there are no new child the old child is removed */ fiber.child = null; nodes.forEach((node, index) => { const key = getKey(node, index); const currentFiber = childKeyMap.get(key); if (currentFiber) { // delete the currentFiber from map, so we can use map to remove pending elements childKeyMap.delete(key); } const previousFiber = refFiber; // create fiber if required and link it refFiber = createAndLink( node, // $FlowFixMe: property a is added for some weird chrome de-optimizaion issue { parentNode, previousSibling, /** * Surprisingly an undefined dummy property increases V8 performance. * We remove nextSibling which when being undefined was running faster * Looks like inlining and deopt issue. But still have to figure out why * undefined property changed anything * Inlining this whole body inside processFiber was not giving issue without * undefined property */ a: undefined, firstDOMNode, isArrayNode: true, nodeIndex: index, parentArrayPart: part.isArrayNode ? part : null, }, currentFiber, previousFiber, fiber, ); // reset the sibling fiber on the ref fiber. This will be set on next iteration of loop. refFiber.sibling = null; /** * if current fiber nodeIndex is different than new index,or if it is a new fiber without any alternate * mark fiber and its previous fiber to have uncommitted placement effect */ // $FlowFixMe: we only have to check for array part here if (currentFiber && currentFiber.part.nodeIndex !== index) { markPendingEffect(refFiber, EFFECT_TYPE_PLACEMENT); // mark the previous fiber as well having the placement effect, as it makes it easier to // rearrange the dom nodes if (index !== 0) markPendingEffect(previousFiber, EFFECT_TYPE_PLACEMENT); } }); // mark non used node to tear down childKeyMap.forEach((fiber) => { markToTearDown(fiber); }); // mark the array fiber for handling effects markPendingEffect(fiber, EFFECT_TYPE_OTHER); } ================================================ FILE: src/processComponentFiber.js ================================================ // @flow import { cloneChildrenFibers, createAndLink, resetToCommittedChild, markPendingEffect, setCurrentComponentFiber, } from './fiber'; import functionalComponentInstance from './functionalComponentInstance'; import { CLASS_COMPONENT_NODE, isComponentNode } from './brahmosNode'; import { getClosestSuspenseFiber, resetSiblingFibers } from './circularDep'; import { callLifeCycle, getComponentName, BrahmosRootComponent, afterCurrentStack } from './utils'; import { getPendingUpdates, withUpdateSource } from './updateUtils'; import shallowEqual from './helpers/shallowEqual'; import { BRAHMOS_DATA_KEY, EFFECT_TYPE_OTHER, UPDATE_TYPE_DEFERRED } from './configs'; import { Component } from './Component'; import type { Fiber, ErrorInfo, AllContext, ClassComponentBrahmosData, ComponentBrahmosData, ComponentClassInstance, ClassComponentUpdate, } from './flow.types'; export function getErrorBoundaryFiber(fiber: Fiber): ?Fiber { const { root } = fiber; while ( (fiber = fiber.parent) && !( fiber.nodeInstance instanceof Component && (fiber.nodeInstance.componentDidCatch || fiber.node.type.getDerivedStateFromError) ) ) { if (fiber === root) return null; } return fiber; } export function getErrorInfo(fiber: Fiber): ErrorInfo { let error = ''; while (fiber) { const { node } = fiber; if (node && isComponentNode(node) && node.type !== BrahmosRootComponent) { error += ` at ${getComponentName(node.type)} \n`; } fiber = fiber.parent; } return { componentStack: error, }; } function getCurrentContext(fiber: Fiber): AllContext { const { node: { type: Component }, nodeInstance, parent, } = fiber; // if component has createContext index, we treat it as provider const { __ccId } = Component; const context = parent.context || {}; // if component is not a provider return the same context if (!__ccId) return context; // for new provider instance create a new context by extending the parent context const newContext = Object.create(context); // store the nodeInstance newContext[__ccId] = nodeInstance; return newContext; } function getUpdatedState(prevState, updates) { return updates.reduce((combinedState, { state }) => { if (typeof state === 'function') state = state(combinedState); return { ...combinedState, ...state }; }, prevState); } // method to reset work loop to a fiber of given component function resetLoopToComponentsFiber(fiber) { const { root, nodeInstance } = fiber; // mark component as dirty, so it can be rendered again nodeInstance[BRAHMOS_DATA_KEY].isDirty = true; // set the alternate fiber as retry fiber, as root.retryFiber = fiber; } export default function processComponentFiber(fiber: Fiber): void { const { node, part, root, childFiberError } = fiber; const { type: Component, nodeType, props = {} } = node; const { currentTransition } = root; const isDeferredUpdate = root.updateType === UPDATE_TYPE_DEFERRED; let shouldUpdate = true; let usedMemoizedValues = false; const isClassComponent = nodeType === CLASS_COMPONENT_NODE; /** * Reset the fiber children to a committed child */ resetToCommittedChild(fiber); /** If Component instance is not present on node create a new instance */ let { nodeInstance } = fiber; let isFirstRender = false; if (!nodeInstance) { // create an instance of the component nodeInstance = isClassComponent ? new Component(props) : functionalComponentInstance(Component); // keep the reference of instance to the node. fiber.nodeInstance = nodeInstance; isFirstRender = true; } // const brahmosData: ComponentBrahmosData = nodeInstance[BRAHMOS_DATA_KEY]; // get current context const context = getCurrentContext(fiber); // store context in fiber fiber.context = context; /** * If it is a class component, * associate state, props to component instance * and call all the life cycle method which comes before rendering. */ if (isClassComponent) { const componentClassInstance = ((nodeInstance: any): ComponentClassInstance); const classBrahmosData = ((brahmosData: any): ClassComponentBrahmosData); const { committedValues, memoizedValues } = classBrahmosData; // if it is first render we should store the initial state on committedValues if (isFirstRender) committedValues.state = componentClassInstance.state; let { props: prevProps, state: prevState } = committedValues; if ( memoizedValues && currentTransition && currentTransition.transitionId === memoizedValues.transitionId ) { ({ props: prevProps, state: prevState } = memoizedValues); usedMemoizedValues = true; } // /** * reset the component instance values to prevProps and prevState, * The state and prop value might have been effected by deferred rendering * For sync render it should point to previous committed value, and for * deferred render it should point to memoized values */ componentClassInstance.props = prevProps; componentClassInstance.state = prevState; const { shouldComponentUpdate } = componentClassInstance; let state = prevState; // apply the pending updates in state if const pendingUpdates = ((getPendingUpdates(fiber): any): Array); if (pendingUpdates.length) state = getUpdatedState(prevState, pendingUpdates); const checkShouldUpdate = !isFirstRender && root.forcedUpdateWith !== nodeInstance; // call getDerivedStateFromProps lifecycle with the unCommitted state and apply the derivedState on state const derivedState = callLifeCycle(Component, 'getDerivedStateFromProps', [props, state]); const derivedErrorState = childFiberError ? callLifeCycle(Component, 'getDerivedStateFromError', [childFiberError.error]) : undefined; if (derivedState || derivedErrorState) { // $FlowFixMe state = { ...state, ...derivedState, ...derivedErrorState }; } /** * check if component is instance of PureComponent, if yes then, * do shallow check for props and states */ if (componentClassInstance.isPureReactComponent && checkShouldUpdate) { shouldUpdate = !shallowEqual(state, prevState) || !shallowEqual(props, prevProps); } /** * check if component should update or not. If PureComponent shallow check has already * marked component to not update then we don't have to call shouldComponentUpdate * Also we shouldn't call shouldComponentUpdate on first render */ if (shouldComponentUpdate && shouldUpdate && checkShouldUpdate) { shouldUpdate = shouldComponentUpdate.call(componentClassInstance, props, state); } /** * If it is a context consumer add provider on the props */ const { contextType } = Component; if (contextType) { const { id, defaultValue } = contextType; const provider = context[id]; const contextValue = provider ? provider.props.value : defaultValue; // if it is a first render subscribe component for provider value change if (provider && isFirstRender) { provider.sub(componentClassInstance); } componentClassInstance.context = contextValue; } // set the new state, props, context and reset uncommitted state componentClassInstance.state = state; // $FlowFixMe: We are just setting the existing prop, so we can ignore the error componentClassInstance.props = props; // call setState callbacks after its set to component instance pendingUpdates.forEach(({ callback }) => { if (callback) { // call the callback with same update source as callbacks can have setState, and we want to reduce repaints const { updateSource } = root; afterCurrentStack(() => { withUpdateSource(updateSource, () => callback(state)); }); } }); // store the state and props on memoized value as well if (currentTransition) { classBrahmosData.memoizedValues = { state, props, transitionId: currentTransition.transitionId, }; } } // render the nodes if (shouldUpdate) { // set the current component fiber we are processing setCurrentComponentFiber(fiber); // increment the render count. This is to track how many times a component is rendered in a render cycle brahmosData.renderCount += 1; // if the component is error boundary with error and it does not have getDerivedStateFromError, render null const hasNonHandledError = childFiberError && !Component.getDerivedStateFromError; try { if (hasNonHandledError) { brahmosData.nodes = null; } else { // else get the new elements nodeInstance.__render(props); } } catch (error) { const errorBoundary = getErrorBoundaryFiber(fiber); // TODO: handle error boundaries // if error is a suspender, handle the suspender in suspense component // TODO: this is very basic case for suspender, add better code to check if it is a suspender if (typeof error.then === 'function') { const suspenseFiber = getClosestSuspenseFiber(fiber); /** * if there is no suspense in parent hierarchy throw error that suspender can't be * used outside of suspense * TODO: think for better message */ if (!suspenseFiber) { throw new Error(`Rendering which got suspended can't be used outside of suspense.`); } suspenseFiber.nodeInstance.handleSuspender(error, suspenseFiber); // reset the work loop to suspense fiber or suspense list fiber, if it has suspense list as parent const resetFiber = resetSiblingFibers(suspenseFiber); resetLoopToComponentsFiber(resetFiber); /** * else if there is any error boundary handle the error in error boundary * It should not handle error if its already been handled once */ } else if (errorBoundary && !errorBoundary.childFiberError) { const errorInfo = getErrorInfo(fiber); // log the error and retry rendering console.error(error); const errorDetail = `The above error occurred in the <${getComponentName( node.type, )}> component: \n${errorInfo.componentStack}`; console.error(errorDetail); errorBoundary.childFiberError = { error, errorInfo }; // reset the work loop to errorBoundary fiber resetLoopToComponentsFiber(errorBoundary); // else throw error } else { throw error; } return; } finally { // reset the component fiber setCurrentComponentFiber(null); // if it class component reset the state and prop to committed value const brahmosData = nodeInstance[BRAHMOS_DATA_KEY]; if (isClassComponent && isDeferredUpdate) { const { committedValues } = ((brahmosData: any): ClassComponentBrahmosData); Object.assign(((nodeInstance: any): ComponentClassInstance), committedValues); } } } /** * If the new rendered node is different from the previously render node * create new child, link it and mark as pending effect * * shouldUpdate can be false in case we are using memoized value, * so in such case as well try to create new child. */ if (usedMemoizedValues || shouldUpdate) { const { child } = fiber; const { nodes: newNodes } = brahmosData; if (!child || child.node !== newNodes) { // component will always return a single node so we can pass the previous child as current fiber createAndLink(newNodes, part, child, fiber, fiber); // mark that the fiber has uncommitted effects markPendingEffect(fiber, EFFECT_TYPE_OTHER); } else if (!usedMemoizedValues) { /** * if we don't need to update the child fibers, we should clone the child fiber from current tree * But if we have memoized props and states and no update is required, it means we already are * pointing to correct child fibers, in which case we shouldn't clone the child */ cloneChildrenFibers(fiber); } } else { cloneChildrenFibers(fiber); } } ================================================ FILE: src/processTagFiber.js ================================================ // @flow import { TAG_ELEMENT_NODE, ATTRIBUTE_NODE } from './brahmosNode'; import { createAndLink, cloneChildrenFibers, markPendingEffect } from './fiber'; import getTagNode from './TagNode'; import TemplateNode from './TemplateNode'; import { getEffectiveAttrName, loopEntries } from './utils'; import { RESERVED_ATTRIBUTES, EFFECT_TYPE_OTHER } from './configs'; import type { Fiber, AttributePart } from './flow.types'; function isAttrOverridden(tagAttrs, attrName, attrIndex) { const lastIndex = tagAttrs.lastIndexOf(attrName); // if attrIndex is before the last attribute with same name it means its overridden return attrIndex <= lastIndex; } function attributeNode(props, ref) { return { nodeType: ATTRIBUTE_NODE, props, ref, }; } function partsToFiber(parts, values, parentFiber) { let refFiber = parentFiber; let oldChildFiber = parentFiber.child; for (let i = 0, ln = parts.length; i < ln; i++) { let part = parts[i]; const value = values[i]; let node; if (part.isAttribute) { const { domNode } = part; // mix all the consecutive attributes if they belong to same domNode const dynamicAttributes = {}; let refValue; while (part && domNode === part.domNode) { loopEntries(values[i], (attrName, attrValue) => { const attributePart = ((part: any): AttributePart); const effectiveAttrName = getEffectiveAttrName(attrName); const isOverridden = isAttrOverridden( attributePart.tagAttrs, effectiveAttrName, attributePart.attrIndex, ); if (!isOverridden && !RESERVED_ATTRIBUTES[attrName]) { dynamicAttributes[attrName] = attrValue; } else if (attrName === 'ref') { // Note only functional refs are supported refValue = attrValue; } }); part = parts[++i]; } // reduce the counter to correct the loop index. As it is extra incremented in while loop i--; part = parts[i]; node = attributeNode(dynamicAttributes, refValue); // $FlowFixMe: Not sure why flow is not able to infer this } else if (part.isNode) { node = value; } /** * create a fiber from node and link it to reference fiber */ // get the current old fiber refFiber = createAndLink(node, part, oldChildFiber, refFiber, parentFiber); // set the next old child to oldChildFiber oldChildFiber = oldChildFiber && oldChildFiber.sibling; } } /** * Update tagged template node */ export default function processTagFiber(fiber: Fiber): void { const { node } = fiber; const { part, alternate, parent: { context }, } = fiber; const oldNode = alternate && alternate.node; const { values, nodeType } = node; const isTagElement = nodeType === TAG_ELEMENT_NODE; // if the node is an svg element, or fiber is already mentioned as svgPart set the isSvgPart true const isSvgPart = fiber.isSvgPart || node.type === 'svg'; // store isSvgPart info back to fiber, this will be forwarded to children fiber.isSvgPart = isSvgPart; let { nodeInstance } = fiber; if (!nodeInstance) { nodeInstance = isTagElement ? getTagNode(node, isSvgPart) : new TemplateNode(node.template, isSvgPart); // add nodeInstance to node so we can access it on next updates fiber.nodeInstance = nodeInstance; } /** * if any of nodeInstance part does not have proper parent node and its not first render * patch the part information using the current node's part * This can happen if the parent node is fragment, or it is the first or last item */ if (!isTagElement) { // $FlowFixMe: We only patchParts in TemplateNode nodeInstance.patchParts(part); } /** * Associate parts to fiber. * No need to perform this if node and oldNode are same * This will only happen when we are just doing position change * In which case just clone the children fibers * CHECK: Will there be ever any case where node is same as oldNode * and process is called. */ if (node !== oldNode) { if (isTagElement) { createAndLink(node.props.children, nodeInstance.parts[0], fiber.child, fiber, fiber); } else { partsToFiber(nodeInstance.parts, values, fiber); } } else { cloneChildrenFibers(fiber); } // mark that the fiber has uncommitted effects markPendingEffect(fiber, EFFECT_TYPE_OTHER); // attach context on fiber fiber.context = context; } ================================================ FILE: src/processTextFiber.js ================================================ // @flow import { markPendingEffect } from './fiber'; import { EFFECT_TYPE_OTHER } from './configs'; import type { Fiber } from './flow.types'; export function processTextFiber(fiber: Fiber): void { const { node, alternate } = fiber; const oldNode = alternate && alternate.node; // if text is different then only we should add it as an effect if (node !== oldNode) { // mark that the fiber has uncommitted effects markPendingEffect(fiber, EFFECT_TYPE_OTHER); } } ================================================ FILE: src/reRender.js ================================================ // @flow import { UPDATE_SOURCE_IMMEDIATE_ACTION, UPDATE_SOURCE_TRANSITION, UPDATE_TYPE_SYNC, } from './configs'; import { getCurrentUpdateSource, getCurrentTransition, getUpdateType } from './updateUtils'; import { PREDEFINED_TRANSITION_DEFERRED } from './transitionUtils'; import { setUpdateTime, getFiberFromComponent } from './fiber'; import { doSyncProcessing, doDeferredProcessing } from './workLoop'; import { afterCurrentStack } from './utils'; import type { AnyComponentInstance } from './flow.types'; /** * Method to rerender a given component * In case of reRender, start from the root, * clone the current fiber to wip, and use the wip which is pointing * to children of current tree. */ export default function reRender(component: AnyComponentInstance): void { const fiber = getFiberFromComponent(component); const { root } = fiber; const { pendingTransitions, batchUpdates } = root; const currentUpdateSource = getCurrentUpdateSource(); const currentTransition = getCurrentTransition(); const updateType = getUpdateType(); // set updateTime on fiber parent hierarchy based on updateType setUpdateTime(fiber, updateType); /** * if the update source is transition, and the transition is not already present in pending list * add the transition in pending transition */ if ( currentUpdateSource === UPDATE_SOURCE_TRANSITION && !pendingTransitions.includes(currentTransition) ) { /** * If it is predefined deferred transition, we need to add current transition * as first item as PREDEFINED_TRANSITION_DEFERRED has more priority * or else add it in last of pendingTransitions */ if (currentTransition === PREDEFINED_TRANSITION_DEFERRED) { pendingTransitions.unshift(currentTransition); } else { pendingTransitions.push(currentTransition); } } /** * if there is already a batch update happening, increment the reRender count and * early return as all the state change will be covered with that batch update */ if (batchUpdates[currentUpdateSource]) { batchUpdates[currentUpdateSource] += 1; return; } batchUpdates[currentUpdateSource] = 1; afterCurrentStack(() => { const reRenderCount = batchUpdates[currentUpdateSource]; // reset batch update so it can start taking new updates batchUpdates[currentUpdateSource] = 0; const isDeferredUpdate = currentUpdateSource === UPDATE_SOURCE_TRANSITION; /** * Don't try to do deferred rendering if a sync render is pending, * as deferred rendering happens after sync render */ if (isDeferredUpdate && root.lastCompleteTime < root.updateTime) return; root.updateSource = currentUpdateSource; if (isDeferredUpdate) { doDeferredProcessing(root); } else { /** * if the update source is event and we don't have any ongoing sync update * which we can figure out based on last updateType and if there is any cancelSchedule * Start the processing from the fiber which cause the update. * * Also, when reRenderCount is more than one it means there are multiple update pending */ const hasOngoingSyncUpdates = root.updateType === UPDATE_TYPE_SYNC && root.cancelSchedule; const startFromFiber = currentUpdateSource === UPDATE_SOURCE_IMMEDIATE_ACTION && !hasOngoingSyncUpdates && reRenderCount === 1; doSyncProcessing(startFromFiber ? fiber : root.current); } }); } ================================================ FILE: src/reactEvents.js ================================================ import { getNodeName } from './utils'; import { syncUpdates } from './updateUtils'; import { RENAMED_EVENTS, ONCHANGE_INPUT_TYPES } from './configs'; export function getEffectiveEventName(eventName, node) { const { type } = node; const nodeName = getNodeName(node); if (RENAMED_EVENTS[eventName]) return RENAMED_EVENTS[eventName]; /** * Logic Source: (https://github.com/preactjs/preact/blob/master/src/constants.js) */ return /^change(textarea|input)/i.test(eventName + nodeName) && !ONCHANGE_INPUT_TYPES.test(type) ? 'input': eventName; } export function getInputStateType(node) { const { type } = node; const nodeName = getNodeName(node); if (nodeName === 'input' && (type === 'radio' || type === 'checkbox')) { return 'checked'; } else if (nodeName === 'input' || nodeName === 'select' || nodeName === 'textarea') { return 'value'; } } export function handleControlledReset(node) { const inputStateType = getInputStateType(node); // if it is not an controlled input type return if (!inputStateType) return; const propValue = node[`${inputStateType}Prop`]; const value = node[inputStateType]; if (propValue !== undefined && propValue !== value) { node[inputStateType] = propValue; } } export function handleInputProperty(inputStateType, node, attrName, attrValue) { /** * if we are passing checked prop / value prop, set the value and also store the prop value * to the node so we can check if the element is controlled or not, and in controlled element * reset the property to the property passed as stored prop * * For other properties just set it * */ if (inputStateType === 'checked') { if (attrName === 'checked') { node.checked = attrValue; node.checkedProp = attrValue; } else if (attrName === 'defaultChecked' && node.checkedProp === undefined) { node.checked = attrValue; } else { node[attrName] = attrValue; } } else if (inputStateType === 'value') { if (attrName === 'value') { node.value = attrValue; node.valueProp = attrValue; } else if (attrName === 'defaultValue' && node.valueProp === undefined) { node.value = attrValue; } else { node[attrName] = attrValue; } } } export function getPatchedEventHandler(node, attrName, handler) { const eventHandlers = node.__brahmosData.events; let eventHandlerObj = eventHandlers[attrName]; /** * if eventHandlerObj is already defined update it with new handler * or else create a new object */ // if (eventHandlerObj) { eventHandlerObj.handler = handler; return eventHandlerObj.patched; } else { eventHandlerObj = eventHandlers[attrName] = { handler, patched: null, }; } eventHandlerObj.patched = function(event) { // if the handler is defined call the handler if (eventHandlerObj.handler) { syncUpdates(() => { eventHandlerObj.handler.call(this, event); }); } }; return eventHandlerObj.patched; } ================================================ FILE: src/refs.js ================================================ // @flow import { getNormalizedProps } from './utils'; import type { ObjectLiteral, Ref, ObjectRef, FunctionalComponent } from './flow.types'; import { REACT_FORWARD_REF } from './configs'; export function forwardRef(Component: FunctionalComponent): FunctionalComponent { function ForwardRefComponent(props: ObjectLiteral) { return Component(getNormalizedProps(props, false), props.ref); } ForwardRefComponent.__isForwardRef = true; ForwardRefComponent.$$typeof = REACT_FORWARD_REF; return ForwardRefComponent; } export function createRef(): ObjectRef { return { current: null }; } /** function to attach ref to the passed ref prop */ export function setRef(ref: Ref, instance: any): void { /** * Note: we only support ref as callback and createRef syntax. * Brahmos does not support string ref as they are deprecated */ if (typeof ref === 'function') { ref(instance); } else if (typeof ref === 'object') { ref.current = instance; } } ================================================ FILE: src/render.js ================================================ // @flow import { createBrahmosNode } from './circularDep'; import { CLASS_COMPONENT_NODE } from './brahmosNode'; import { BrahmosRootComponent } from './utils'; import { createFiber, createHostFiber, setUpdateTime } from './fiber'; import { doSyncProcessing } from './workLoop'; import { syncUpdates, getCurrentUpdateSource } from './updateUtils'; import { UPDATE_TYPE_SYNC } from './configs'; import type { ExtendedElement } from './flow.types'; /** * Method to render a node */ export default function render(node: any, target: ExtendedElement) { let { __rootFiber: rootFiber } = target; let fiber; if (!rootFiber) { const rootNode = createBrahmosNode(BrahmosRootComponent, { children: node }); const part = { parentNode: target, previousSibling: null, isNode: true, }; rootFiber = createHostFiber(target); fiber = createFiber(rootFiber, rootNode, part); // make the rootFiber parent of fiber // $FlowFixMe: rootFiber can only be parent in case of rootNode fiber.parent = rootFiber; // make the root fiber the wip fiber of rootFiber rootFiber.current = fiber; // add root fiber on target target.__rootFiber = rootFiber; } else { /** * Update the children in BrahmosRootComponent node and also reset the processedTime * so it can processed again. */ fiber = rootFiber.current; fiber.node.props.children = node; fiber.processedTime = 0; setUpdateTime(fiber, UPDATE_TYPE_SYNC); } /** * do not schedule in render * NOTE: This will also affect sync setStates inside componentDidMount, or useEffects. * This is expected to prevent multiple repaints */ syncUpdates(() => { rootFiber.updateSource = getCurrentUpdateSource(); doSyncProcessing(fiber); }); // if it is a class component return the component instance, or else return null // $FlowFixMe: As the fiber is a wrapper component, it will always have child return node && node.nodeType === CLASS_COMPONENT_NODE ? fiber.child.nodeInstance : null; } ================================================ FILE: src/scheduler.js ================================================ // @flow import { PREDEFINED_TRANSITION_DEFERRED } from './transitionUtils'; import type { HostFiber } from './flow.types'; type TimeRemaining = () => boolean; const RENDER_SLOT = 5; const MAX_RENDER_SLOT = 30; // We can allow to drop frame rate to minimum 30 fps const FRAME_DURATION_DEFAULT = 16; // Default we try to keep it under 60 fps const DEFERRED_TRANSITION_MAX_RETRY = 300; // close to 5000ms -> 16 * 300 with some buffer const OTHER_TRANSITION_MAX_RETRY = 600; // close to 10000ms -> 16 * 600 with some buffer let firstFrameTime; requestAnimationFrame((time) => { firstFrameTime = time; }); const getTime = () => performance.now(); const timedOutRemaining: TimeRemaining = () => true; const frameRemainingTime = (currentTime, frameDuration) => { frameDuration = frameDuration || FRAME_DURATION_DEFAULT; return frameDuration - ((currentTime - firstFrameTime) % frameDuration); }; /** * create a message channel instead of using setTimeouts * setTimeouts are clamped to ~4ms which make scheduling slow */ let port, channelCallbacks; if (typeof MessageChannel !== 'undefined') { channelCallbacks = []; const channel = new MessageChannel(); channel.port1.onmessage = function() { channelCallbacks.forEach((handle) => handle()); }; port = channel.port2; } function schedule(cb) { /** * If Message channel is not available or frame is about to end use combination of timeout and requestIdleCallback, */ if (!port || frameRemainingTime(getTime()) < 1) { const scheduleCb = () => { cancelSchedule(); cb(); }; /** * Start both timer and request idle callback to schedule processing in next frame * and which ever is called first cancel the other one */ const timeoutId = setTimeout(scheduleCb, 1); const ricId = requestIdleCallback(scheduleCb); const cancelSchedule = () => { clearTimeout(timeoutId); cancelIdleCallback(ricId); }; return cancelSchedule; } // If we have enough time use message channel as message channels are called more often channelCallbacks.push(cb); port.postMessage(null); return () => { const index = channelCallbacks.indexOf(cb); if (index !== -1) channelCallbacks.splice(index, 1); }; } export default function scheduleTask( root: HostFiber, shouldSchedule: boolean, cb: (timeRemaining: TimeRemaining) => void, ) { const { cancelSchedule } = root; // cancel any existing scheduled task on root if (cancelSchedule) { cancelSchedule(); root.cancelSchedule = null; } if (shouldSchedule) { root.cancelSchedule = schedule(() => { const { currentTransition } = root; const tryCount = currentTransition ? currentTransition.tryCount : 0; /** * we get to limit retries in deferred update, otherwise deferred updated * can starve by a sync update. So eventually deferred update has to be flushed. */ const maxAllowedRetry = currentTransition === PREDEFINED_TRANSITION_DEFERRED ? DEFERRED_TRANSITION_MAX_RETRY : OTHER_TRANSITION_MAX_RETRY; const slotStartTime = getTime(); /** * We keep incrementing slot time based on tryCount max to MAX_RENDER_SLOT. * This may reduce the frame rate, but allows the deferred update to execute. * We are clamping the minimum frame rate to 30fps, so increasing frame time is * fine. */ const slotTime = Math.min(MAX_RENDER_SLOT, RENDER_SLOT + tryCount); /** * For a render slot time we round frameDuration to nearest frame size. */ const frameDuration = Math.floor(slotTime / FRAME_DURATION_DEFAULT) * FRAME_DURATION_DEFAULT; const timeRemaining = () => { const currentTime = getTime(); /** * Make sure we don't eat up a frame, so clamping it to remaining frame time is required. * Its not perfect as its heuristic based on initial frame time, but does the job when * combined with requestIdleCallback */ const maxSlotTime = frameRemainingTime(currentTime, frameDuration); const slotEndTime = slotStartTime + Math.min(RENDER_SLOT, maxSlotTime); return currentTime < slotEndTime; }; // If we cross max allowed retry we need to flush render synchronously cb(tryCount > maxAllowedRetry ? timedOutRemaining : timeRemaining); }); return; } cb(timedOutRemaining); } ================================================ FILE: src/tags.js ================================================ // @flow import TemplateTag from './TemplateTag'; import { brahmosNode, TAG_NODE } from './brahmosNode'; import type { TemplateTagType, BrahmosNode } from './flow.types'; type TagReturn = (partMetaCode: string) => BrahmosNode; const templateTagCache = new WeakMap(); export function createTagNode(template: TemplateTagType, values: Array): BrahmosNode { const node = brahmosNode(null, values, undefined); node.nodeType = TAG_NODE; node.template = template; return node; } export function html(strings: Array, ...values: Array): TagReturn { return (partMetaCode) => { let template = templateTagCache.get(strings); if (!template) { template = new TemplateTag(strings, partMetaCode); templateTagCache.set(strings, template); } return createTagNode(template, values); }; } ================================================ FILE: src/tearDown.js ================================================ // @flow import { callLifeCycle, remove, getNextSibling, isMounted } from './utils'; import { isComponentNode, isRenderableNode, isTagNode, CLASS_COMPONENT_NODE, isPrimitiveNode, } from './brahmosNode'; import { setRef } from './refs'; import { cleanEffects } from './hooks'; import type { HostFiber } from './flow.types'; function tearDownChild(child, part, _isTagNode, removeDOM) { /** * if we got a tag to remove for child nodes we don't need to remove those * nodes in child fibers as it will be remove by current fiber * Note, some child node can be outside of the dom boundary a TagNode is covering * So we should check if the parent node of the child is not same as the tagNode. */ let _removeDOM = child.part.parentNode !== part.parentNode && _isTagNode ? false : removeDOM; // if a child is portal then we should keep the remove dom to true const { node } = child; if (node && node.portalContainer) { _removeDOM = true; } tearDownFiber(child, _removeDOM); } function tearDownFiber(fiber, removeDOM) { const { node, part, nodeInstance } = fiber; /** * mark the fiber shouldTearDown to false to avoid extra teardown in case * same fiber is pushed twice, this can happen when we looped in between the * render cycle */ fiber.shouldTearDown = false; // bail out shouldTearDown is false or if node is non-renderable node if (!isRenderableNode(node)) return; // recurse to the children and tear them down first const _isTagNode = isTagNode(node); let { child } = fiber; if (child) { tearDownChild(child, part, _isTagNode, removeDOM); while (child.sibling) { child = child.sibling; tearDownChild(child, part, _isTagNode, removeDOM); } } // if it is primitive node we need to delete the text node associated with it if (isPrimitiveNode(node) && removeDOM) { const textNode = getNextSibling(part.parentNode, part.previousSibling); if (textNode) remove(textNode); return; } const { ref } = node; /** * We have to only handle tag, component and attributes, * as tag has elements to remove, attribute fiber can have ref to unset * and component can have ref and lifecycle method to call. * * Text nodes will be remove by removing its parent tag node. So no * need to handle text node separately */ /** * This will cover ATTRIBUTE_NODE as well, so no need to handle them separately */ if (ref) { setRef(ref, null); } // if there is no nodeInstance it means its not rendered yet so no need do anything on that // NOTE: This has to be after ref logic as attribute nodes do not have nodeInstance but setRef has to be done if (!nodeInstance) return; // if it is a tag node remove the dom elements added by tag node if (_isTagNode) { const { domNodes } = nodeInstance; // remove all the elements of nodeInstance if (removeDOM) remove(domNodes); } // if its a component node and is mounted then call lifecycle methods else if (isComponentNode(node) && isMounted(nodeInstance)) { // for class component call componentWillUnmount and for functional comp clean effects if (node.nodeType === CLASS_COMPONENT_NODE) { callLifeCycle(nodeInstance, 'componentWillUnmount'); } else { cleanEffects(fiber, true); } } } export default function(root: HostFiber): void { const { tearDownFibers } = root; tearDownFibers.forEach((fiber) => { // only tear down those fibers which are marked for tear down if (fiber.shouldTearDown) tearDownFiber(fiber, true); }); // rest the tear down fibers root.tearDownFibers = []; } ================================================ FILE: src/transitionUtils.js ================================================ // @flow import { getUniqueId } from './utils'; import { UPDATE_SOURCE_TRANSITION, TRANSITION_STATE_START, TRANSITION_STATE_SUSPENDED, TRANSITION_STATE_RESOLVED, TRANSITION_STATE_COMPLETED, TRANSITION_STATE_TIMED_OUT, } from './configs'; import type { PredefinedTransition, Transition, AnyTransition, Fiber, HostFiber, } from './flow.types'; export const PREDEFINED_TRANSITION_SYNC: PredefinedTransition = { transitionId: '', tryCount: 0, transitionState: TRANSITION_STATE_TIMED_OUT, }; export const PREDEFINED_TRANSITION_DEFERRED: PredefinedTransition = { transitionId: getUniqueId(), tryCount: 0, transitionState: TRANSITION_STATE_TIMED_OUT, }; function shouldProcessTransition(transition: AnyTransition) { const { transitionState } = transition; return ( transitionState === TRANSITION_STATE_START || transitionState === TRANSITION_STATE_RESOLVED || transitionState === TRANSITION_STATE_TIMED_OUT ); } export function isTransitionCompleted(transition: AnyTransition): boolean { const { transitionState } = transition; return ( transitionState === TRANSITION_STATE_COMPLETED || transitionState === TRANSITION_STATE_TIMED_OUT ); } export function isTransitionResolved(transition: Transition): boolean { const { transitionState } = transition; // send true, on either the transition is resolved or completed return transitionState === TRANSITION_STATE_RESOLVED || isTransitionCompleted(transition); } export function setTransitionComplete(transition: AnyTransition): void { const { transitionState } = transition; if ( transitionState !== TRANSITION_STATE_TIMED_OUT && transitionState !== TRANSITION_STATE_SUSPENDED ) { /** * If transition is in pending state, first reset the isPending state * and then on next render cycle mark transition as completed * so that isPending and transition changes can be shown on one commit phase */ if (transition.isPending) { transition.clearTimeout(); transition.updatePendingState(false, UPDATE_SOURCE_TRANSITION); } else { // $FlowFixMe: We check if its not timed out already, so we can ignore flowtype error here transition.transitionState = TRANSITION_STATE_COMPLETED; } } } /** * get current transition id from the current rendering */ export function getTransitionFromFiber( fiber: Fiber, defaultTransition: ?PredefinedTransition, ): AnyTransition { defaultTransition = defaultTransition || PREDEFINED_TRANSITION_SYNC; // if there is currentTransition return that or else return SYNC transition return fiber.root.currentTransition || defaultTransition; } /** * function to get first pending transition */ export function getFirstTransitionToProcess(root: HostFiber): ?AnyTransition { const { pendingTransitions } = root; return pendingTransitions.find(shouldProcessTransition); } /** * function to check if a transition is a custom transition */ export function isCustomTransition(transition: Transition): boolean { return !!transition.startTransition; } ================================================ FILE: src/unmountComponentAtNode.js ================================================ // @flow import tearDown from './tearDown'; import { markToTearDown } from './fiber'; import type { ExtendedElement } from './flow.types'; function unmountComponentAtNode(container: ExtendedElement): boolean { /** * Most of the time we only to unmount component from the root elem * TODO: Should we support unmounting from any element rendered by react itself. Check if React allows that */ const { __rootFiber: root } = container; if (root) { // tear down the current tree markToTearDown(root.current); tearDown(root); // remove the __rootFiber reference container.__rootFiber = undefined; return true; } return false; } export default unmountComponentAtNode; ================================================ FILE: src/updateAttribute.js ================================================ import { getEffectiveAttrName, getEventName, isEventAttribute, loopEntries } from './utils'; import { getEffectiveEventName, getInputStateType, handleInputProperty, getPatchedEventHandler, handleControlledReset, } from './reactEvents'; import { XLINK_NS, IS_NON_DIMENSIONAL } from './configs'; /** * Lot of diffing and applying attribute logic in this file is inspired/forked from Preact * Reference: * https://github.com/preactjs/preact/blob/master/src/diff/props.js */ function applyDiffProperty(newObj, oldObj, resetValue, cb) { oldObj = oldObj || {}; // add new attributes loopEntries(newObj, (key, value) => { const oldValue = oldObj[key]; if (value !== oldValue) { cb(key, value, oldValue); } }); // remove old attributes loopEntries(oldObj, (key, value) => { if (newObj[key] === undefined && value !== undefined) { cb(key, resetValue, value); } }); } function setAttribute(node, attrName, attrValue, oldAttrValue, isSvgAttribute) { /** * children attrName is not a valid attribute, if the attrName is coming as children * ignore it. This happens when we are the brahmos nodeType is tag element node. * We don't remove children from props to avoid extra object creation. */ if (attrName === 'children') return; const isEventAttr = isEventAttribute(attrName); // Handle event attributes if (isEventAttr) { // Reference https://github.com/facebook/react/blob/master/packages/react-dom/src/events/DOMPluginEventSystem.js#L498 const isCaptureEvent = attrName.substr(-7) === 'Capture' && attrName.substr(-14, 7) === 'Pointer'; let eventName = getEventName(attrName, isCaptureEvent); eventName = getEffectiveEventName(eventName, node); // get patched event handler const patchedHandler = getPatchedEventHandler(node, attrName, attrValue); // if new event handler is not there but it had old handler, remove the old one if (oldAttrValue && !attrValue) { node.removeEventListener(eventName, patchedHandler, isCaptureEvent); // if the event is getting added first time add a listener } else if (!oldAttrValue && attrValue) { node.addEventListener(eventName, patchedHandler, isCaptureEvent); } // handle style attributes } else if (attrName === 'style') { const { style } = node; applyDiffProperty(attrValue || {}, oldAttrValue, '', (key, value) => { /** * check if it is custom property (--some-custom-property),then use setProperty to assign value * otherwise just add the property in style object */ if (key[0] === '-') { style.setProperty(key, value); } else { style[key] = typeof value === 'number' && IS_NON_DIMENSIONAL.test(key) === false ? value + 'px' : value; } }); // handle dangerously set innerHTML } else if (attrName === 'dangerouslySetInnerHTML') { const oldHTML = oldAttrValue && oldAttrValue.__html; const newHTML = attrValue && attrValue.__html; if (newHTML !== oldHTML) { node.innerHTML = newHTML == null ? '' : newHTML; // `==` here will check for undefined and null } // handle node properties } else if (attrName in node && !isSvgAttribute) { const inputStateType = getInputStateType(node); /** * if it is input element it has to be handled differently, * otherwise just set the property value */ if (inputStateType) { handleInputProperty(inputStateType, node, attrName, attrValue); } else { node[attrName] = attrValue == null ? '' : attrValue; // `==` here will check for undefined and null } // handle all other node attributes } else { // if attribute name is modified for JSX props, reset the name attrName = getEffectiveAttrName(attrName); /** * If attribute is prefixed with xlink then we have to set attribute with namespace * if attribute value is defined set the new attribute value and if * it is not defined and oldAttribute is present remove the oldAttribute; */ let attrNameWithoutNS = attrName.replace(/^xlink:?/, ''); // if attribute value is null, undefined or false remove the attribute const removeAttribute = attrValue == null || attrValue === false; if (attrName !== attrNameWithoutNS) { attrNameWithoutNS = attrNameWithoutNS.toLowerCase(); if (removeAttribute) { node.removeAttributeNS(XLINK_NS, attrNameWithoutNS); } else { node.setAttributeNS(XLINK_NS, attrNameWithoutNS, attrValue); } } else if (removeAttribute) { node.removeAttribute(attrName); } else { node.setAttribute(attrName, attrValue); } } } export default function updateNodeAttributes(node, attributes, oldAttributes, isSvgAttribute) { applyDiffProperty(attributes, oldAttributes, null, (attrName, attrValue, oldAttrValue) => { setAttribute(node, attrName, attrValue, oldAttrValue, isSvgAttribute); }); // handle controlled input resetting handleControlledReset(node); } ================================================ FILE: src/updateUtils.js ================================================ // @flow import { afterCurrentStack } from './utils'; import { PREDEFINED_TRANSITION_SYNC, PREDEFINED_TRANSITION_DEFERRED, getTransitionFromFiber, } from './transitionUtils'; import { UPDATE_SOURCE_DEFAULT, UPDATE_SOURCE_TRANSITION, UPDATE_SOURCE_IMMEDIATE_ACTION, BRAHMOS_DATA_KEY, UPDATE_TYPE_SYNC, UPDATE_TYPE_DEFERRED, } from './configs'; import { getCurrentComponentFiber } from './fiber'; import type { AnyComponentInstance, HostFiber, Fiber, UpdateSource, AnyTransition, UpdateType, PendingUpdates, ClassComponentUpdate, FunctionalComponentUpdate, } from './flow.types'; export const deferredMeta = { initialized: false, timeout: 0, }; let updateSource = UPDATE_SOURCE_DEFAULT; let currentTransition = PREDEFINED_TRANSITION_SYNC; export function getDeferredMeta() { return { ...deferredMeta }; } export function setUpdateSource(source: UpdateSource) { updateSource = source; } export function getCurrentUpdateSource() { return updateSource; } export function getCurrentTransition() { return currentTransition; } function resetUpdateSource() { /** * reset update source when the current stack execution is done, * This will make sure if there is sync update like event or * force update, or initial render all the sync render and commit phase is done */ afterCurrentStack(() => { updateSource = UPDATE_SOURCE_DEFAULT; }); } /** * Function to execute something in context of custom source */ export function withUpdateSource(source: UpdateSource, cb: Function): void { updateSource = source; cb(); updateSource = UPDATE_SOURCE_DEFAULT; } export function withTransition(transition: AnyTransition, cb: Function): void { const prevTransition = currentTransition; currentTransition = transition; // set update source as a transition before calling callback withUpdateSource(UPDATE_SOURCE_TRANSITION, cb); currentTransition = prevTransition; } export function shouldPreventSchedule(root: HostFiber): boolean { // it should prevent scheduling if immediate update is required return root.updateSource === UPDATE_SOURCE_IMMEDIATE_ACTION; } export function isDeferredUpdate(): boolean { return updateSource === UPDATE_SOURCE_TRANSITION; } export function getUpdateType(): UpdateType { return isDeferredUpdate() ? UPDATE_TYPE_DEFERRED : UPDATE_TYPE_SYNC; } /** * Get the pendingUpdates key in class component instance */ export function getPendingUpdatesKey(updateType: UpdateType) { return updateType === UPDATE_TYPE_DEFERRED ? 'pendingDeferredUpdates' : 'pendingSyncUpdates'; } /** * Get pending states based on update type and current transition */ export function getPendingUpdates(fiber: Fiber): PendingUpdates { const { root: { updateType }, nodeInstance: component, } = fiber; const brahmosData = component[BRAHMOS_DATA_KEY]; const pendingUpdatesKey = getPendingUpdatesKey(updateType); if (updateType === UPDATE_TYPE_SYNC) { return brahmosData[pendingUpdatesKey]; } const currentTransitionId = getTransitionFromFiber(fiber, null).transitionId; return brahmosData[pendingUpdatesKey].filter( (stateMeta) => stateMeta.transitionId === currentTransitionId, ); } // function to trigger deferred updates export function deferredUpdates(cb: Function): void { withTransition(PREDEFINED_TRANSITION_DEFERRED, cb); } /** * function to trigger sync updates which doesn't schedule * And rendered and committed synchronously */ export function syncUpdates(cb: Function): void { withUpdateSource(UPDATE_SOURCE_IMMEDIATE_ACTION, cb); } function getComponentFiberInWorkingTree(fiber, nodeInstance) { const { root } = fiber; while (!(fiber.nodeInstance === nodeInstance)) { fiber = fiber.parent; if (fiber === root) return null; } return fiber; } /** * get guarded update meta details. Through error if setState is called * too many times */ export function guardedSetState( componentInstance: AnyComponentInstance, getStateMeta: (transitionId: string) => ClassComponentUpdate | FunctionalComponentUpdate, ): boolean { let updateType, currentTransition; let shouldRerender = true; const brahmosData = componentInstance[BRAHMOS_DATA_KEY]; const fiber = getCurrentComponentFiber(); /** * if the setState is called while rendering, which will be the case when current fiber is set */ if (fiber) { const { renderCount } = brahmosData; // if render count is more than 50 probably we got into infinite loop if (renderCount > 50) { throw new Error( 'Too many rerender. Check your setState call, this may cause an infinite loop.', ); } const { root } = fiber; // mark the component to retry the components fiber root.retryFiber = getComponentFiberInWorkingTree(fiber, componentInstance); updateType = root.updateType; currentTransition = root.currentTransition || PREDEFINED_TRANSITION_SYNC; // we do not want to rerender in this case as the component fiber will be retried shouldRerender = false; } else { // reset the renderCount if not called during the render phase brahmosData.renderCount = 0; updateType = getUpdateType(); currentTransition = getCurrentTransition(); } const pendingUpdateKey = getPendingUpdatesKey(updateType); const stateMeta = getStateMeta(currentTransition.transitionId); brahmosData[pendingUpdateKey].push(stateMeta); return shouldRerender; } ================================================ FILE: src/utils.js ================================================ // @flow import { BRAHMOS_DATA_KEY, CAMEL_ATTRIBUTES, MODIFIED_ATTRIBUTES } from './configs'; import type { ObjectLiteral, AnyComponentInstance, FunctionalComponent, ClassComponent, } from './flow.types'; /** * Method to identify if a jsx element is a html element or custom component * Taken from https://github.com/babel/babel/blob/master/packages/babel-types/src/validators/react/isCompatTag.js * */ export function isHTMLElement(tagName: string): boolean { // Must start with a lowercase ASCII letter return !!tagName && /^[a-z]/.test(tagName); } export function isEventAttribute(attrName: string): boolean { // must start with on prefix /** * Check inspired by preact. * Benchmark for comparison: https://esbench.com/bench/574c954bdb965b9a00965ac6 */ return attrName[0] === 'o' && attrName[1] === 'n'; } // Convert React's camel-cased attributes to hypen cased. export function getEffectiveAttrName(attrName: string): string { /** * if the attribute is camel cased for react, convert it to lower case and return it * Or else * If the attribute is an modified attribute return the html attribute. */ const hyphenCasedAttribute = CAMEL_ATTRIBUTES.test(attrName) ? attrName.replace(/[A-Z0-9]/, '-$&').toLowerCase() : attrName; return MODIFIED_ATTRIBUTES[attrName] || hyphenCasedAttribute; } // get the node name from the node in lowercase format export function getNodeName(node: HTMLElement): string { return node.nodeName.toLowerCase(); } export function getEventName(attrName: string, isCaptureEvent: boolean): string { if (isCaptureEvent) { attrName = attrName.replace(/Capture$/, ''); } return attrName.replace('on', '').toLowerCase(); } export function isCustomElement(tagName: string): boolean { // Must match html tag check and have a hyphen in the tag name return isHTMLElement(tagName) && tagName.indexOf('-') !== -1; } // check if value is null or undefined export function isNil(val: any): boolean { return val === undefined || val === null; } /** * function to return artificial time, we are using counter * instead of time as Date.now or performance.now is little slower than just a counter * Note: when we add support for SSR, we should have a way to reset the initial time to * not let this initialTime grow infinitely */ let initialTime = 0; export function now(): number { return initialTime++; } /** * Function to return current timestamp */ export function timestamp(): number { return Date.now(); } // add brahmos data container to domNode export function addDataContainer(domNode: Node): void { // add brahmos data container // $FlowFixMe: Adding a key is intentional here domNode.__brahmosData = { events: {}, }; } /** * function to separate props, key and ref */ export function getNormalizedProps(props: ObjectLiteral, includeRef: boolean): ObjectLiteral { // if we don't have to remove anything from props no need to create a new object if (!('key' in props || ('ref' in props && !includeRef))) return props; const newProps = {}; let key; for (key in props) { if (key !== 'key' && (key !== 'ref' || includeRef)) newProps[key] = props[key]; } return newProps; } /** * Function to loop over object entries */ export function loopEntries(obj: ObjectLiteral, cb: (key: string, value: any) => void) { const keys = Object.keys(obj); for (let i = 0, ln = keys.length; i < ln; i++) { const key = keys[i]; const value = obj[key]; cb(key, value); } } /** * Remove nodes from parent */ export function remove(nodes: Array | Node): void { if (!Array.isArray(nodes)) nodes = [nodes]; for (let i = nodes.length - 1; i >= 0; i--) { const node = nodes[i]; // $FlowFixMe: remove is present on all browser except IE11 node.remove(); } } /** * Convert an array like object to array */ export function toArray(list: Iterable, start: ?number) { start = start || 0; return Array.prototype.slice.call(list, start); } /** * Given a object/string crate a node which can be appended. */ export function changeToNode(value: Array | NodeList | Node | string): Node { const isNodeList = value instanceof NodeList; if (value instanceof Node) { return value; // if it is a array of Nodes or NodList return a fragment } else if (Array.isArray(value) || isNodeList) { const fragment = document.createDocumentFragment(); let i = 0; while (value[i]) { // $FlowFixMe: Flow Not able to identify condition if `value instanceof NodeList` stored as value fragment.appendChild(value[i]); // no need to increment on nodeList as nodeList is spliced when elements are moved if (!isNodeList) i += 1; } return fragment; } // In other case it will be string so return a text node // $FlowFixMe: Flow Not able to identify condition if `value instanceof NodeList` stored as value return document.createTextNode(value); } /** * Function to add child nodes before endNode, if it is not defined or null * It will add nodes on the last */ export function insertBefore(parent: Element, end: ?Node, value: any): Array { const node = changeToNode(value); /** * Fragment child nodes gets cleared after its appended to dom. * So if it is fragment keep the reference of all childNodes as array. */ let persistentNode; if (Array.isArray(value)) { // if value was already an array no need to convert document fragment to array persistentNode = value; } else if (node instanceof DocumentFragment) { persistentNode = toArray(node.childNodes); } else { persistentNode = value; } parent.insertBefore(node, end); return persistentNode; } // function to get next sibling based on parent node and previous sibling export function getNextSibling(parentNode: Element, previousSibling: ?Node): ?Node { return previousSibling ? previousSibling.nextSibling : parentNode.firstChild; } /** * Merge newState with old state for components */ export function mergeState(state: ObjectLiteral, newState: any): ObjectLiteral { // if new state is not present or any falsy value, just return the state if (!newState) return state; // allow all type of objects to be spread // this behaviour is similar to react if (typeof newState === 'object') { state = { ...state, ...newState }; } return state; } /** * Function to call life cycle of a given component, or component instance */ export function callLifeCycle(object: any, method: string, args: ?Array): void { if (object[method]) { return object[method].apply(object, args); } } /** * Create an empty text node before a given node */ export function createEmptyTextNode(parent: Node, index: number): ?Node { const nextSibling = index === 0 ? parent.firstChild : parent.childNodes[index]; const textNode = document.createTextNode(''); parent.insertBefore(textNode, nextSibling); return textNode; } /** * Put a code execution in micro task, so that it's executed after current stack */ export const resolvedPromise = Promise.resolve(); export function afterCurrentStack(cb: Function): Promise { return resolvedPromise.then(cb); } /** * Function to create a unique id */ export function getUniqueId(): string { return now() + '-' + Math.random() * 1000000; } /** * Method to get a promise which support suspension of rendering */ export function getPromiseSuspendedValue(promise: Promise) { let status = 'pending'; let result; const suspender = promise.then( (r) => { status = 'success'; result = r; }, (e) => { status = 'error'; result = e; }, ); return { read() { if (status === 'pending') { throw suspender; } else if (status === 'error') { throw result; } else if (status === 'success') { return result; } }, }; } /** function to check if component is mounted */ export function isMounted(component: AnyComponentInstance): boolean { return component[BRAHMOS_DATA_KEY].mounted; } /** function to get component name by its constructor */ export function getComponentName(Component: ClassComponent | FunctionalComponent): string { return Component.displayName || Component.name; } /** * A wrapper component which wraps the render elements */ export function BrahmosRootComponent({ children }: ObjectLiteral): any { return children; } ================================================ FILE: src/workLoop.js ================================================ // @flow import { isComponentNode, isPrimitiveNode, isRenderableNode, isTagNode, ATTRIBUTE_NODE, } from './brahmosNode'; import { UPDATE_SOURCE_TRANSITION, BRAHMOS_DATA_KEY, UPDATE_TYPE_DEFERRED, EFFECT_TYPE_OTHER, UPDATE_TYPE_SYNC, UPDATE_SOURCE_IMMEDIATE_ACTION, } from './configs'; import processComponentFiber from './processComponentFiber'; import { processTextFiber } from './processTextFiber'; import processTagFiber from './processTagFiber'; import effectLoop, { preCommitBookkeeping, resetEffectProperties, removeTransitionFromRoot, } from './effectLoop'; import { shouldPreventSchedule, getPendingUpdates, withUpdateSource } from './updateUtils'; import { getFirstTransitionToProcess, setTransitionComplete, isTransitionCompleted, } from './transitionUtils'; import { getNextFiber, cloneChildrenFibers, getUpdateTimeKey, getLastCompleteTimeKey, cloneCurrentFiber, markToTearDown, markPendingEffect, } from './fiber'; import processArrayFiber from './processArrayFiber'; import tearDown from './tearDown'; import { now } from './utils'; import schedule from './scheduler'; import type { Fiber, HostFiber } from './flow.types'; function fiberHasUnprocessedUpdates(fiber) { const { node, nodeInstance } = fiber; /** * Return if node is not component type or if it is component * which is yet to mount (nodeInstance will be null in such case) */ if (!(isComponentNode(node) && nodeInstance)) return false; return !!getPendingUpdates(fiber).length || nodeInstance[BRAHMOS_DATA_KEY].isDirty; } export function processFiber(fiber: Fiber) { const { node, alternate } = fiber; // if new node is null mark old node to tear down if (!isRenderableNode(node)) { if (alternate) markToTearDown(alternate); return; } const nodeHasUpdates = fiberHasUnprocessedUpdates(fiber); /** * If a fiber is processed and node is not dirty we clone all the children from current tree * * This will not affect the first render as fiber will never be on processed state * on the first render. */ if (fiber.processedTime && !nodeHasUpdates) { // We need to clone children only in case we are doing deferred rendering cloneChildrenFibers(fiber); return; } if (isPrimitiveNode(node)) { processTextFiber(fiber); } else if (Array.isArray(node)) { processArrayFiber(fiber); } else if (isTagNode(node)) { processTagFiber(fiber); } else if (isComponentNode(node)) { processComponentFiber(fiber); } else if (node.nodeType === ATTRIBUTE_NODE) { // nothing to on process phase, just mark that the fiber has uncommitted effects markPendingEffect(fiber, EFFECT_TYPE_OTHER); } // after processing, set the processedTime to the fiber fiber.processedTime = now(); } function shouldCommit(root) { // if there is transition processed on work loop check if its completed if (root.currentTransition) { /** * all sync changes should be committed before committing transition, * for a transition to be committed it shouldn't have any pending commits * if not no need to run the commit phase */ return ( root.lastCompleteTime >= root.updateTime && root.hasUncommittedEffect && isTransitionCompleted(root.currentTransition) ); } // otherwise return true for sync commits return true; } /** * This is the part where all the changes are flushed on dom, * It will also take care of tearing the old nodes down */ // Use object notation to avoid inlining of commit changes fn in workloop const avoidInlineCommitChange = { fn: (root) => { const { updateType, current } = root; const lastCompleteTimeKey = getLastCompleteTimeKey(updateType); // tearDown old nodes tearDown(root); const fibersWithEffect = preCommitBookkeeping(root); /** * set the last updated time for render * NOTE: We do it before effect loop so if there is * setStates in effect updateTime for setState should not * fall behind the complete time * * Also, lastCompleteTime should be marked always * weather its deferred or sync updates */ root[lastCompleteTimeKey] = root.lastCompleteTime = now(); // if it deferred swap the wip and current tree if (updateType === UPDATE_TYPE_DEFERRED) { // $FlowFixMe: wip fiber is set after deferred render root.current = root.wip; root.wip = current; } // After correcting the tree flush the effects on new fibers /** * There can be state updates inside effects/lifecycle, so we should mark it as * immediate update so we don't have multiple paints */ withUpdateSource(UPDATE_SOURCE_IMMEDIATE_ACTION, () => effectLoop(root, fibersWithEffect)); }, }; export default function workLoop(fiber: Fiber, topFiber: Fiber | HostFiber) { const { root } = fiber; const { updateType, currentTransition } = root; const lastCompleteTimeKey = getLastCompleteTimeKey(updateType); const updateTimeKey = getUpdateTimeKey(updateType); const lastCompleteTime = root[lastCompleteTimeKey]; /** * If the update is triggered from update source which needs to be flushed * synchronously we don't need requestIdleCallback, in other case we should * schedule our renders. */ const shouldSchedule = !shouldPreventSchedule(root); schedule(root, shouldSchedule, (timeRemaining) => { while (fiber !== topFiber) { // process the current fiber which will return the next fiber /** * If there is time remaining to do some chunk of work, * process the current fiber, and then move to next * and keep doing it till we are out of time. */ if (timeRemaining()) { processFiber(fiber); /** * if the fiber jump due to suspense or error boundary, * we need to use that as next fiber. We also need to reset * topFiber to root, as the retry fiber can be in upper hierarchy */ const { retryFiber } = root; if (retryFiber) { fiber = retryFiber; topFiber = root; root.retryFiber = null; } else { fiber = getNextFiber(fiber, topFiber, lastCompleteTime, updateTimeKey); } } else { // if we are out of time schedule work for next fiber workLoop(fiber, topFiber); return; } } // call all the render callbacks root.callRenderCallbacks(); if (currentTransition) { // set transition complete if it is not on suspended or timed out state setTransitionComplete(currentTransition); // reset try count currentTransition.tryCount = 0; /** * if transition is completed and it does not have any effect to commit, we should remove the * transition from pending transition */ if (!root.hasUncommittedEffect && isTransitionCompleted(currentTransition)) { removeTransitionFromRoot(root); } } if (shouldCommit(root)) { avoidInlineCommitChange.fn(root); } // check if there are any pending transition, if yes try rendering them if (getFirstTransitionToProcess(root)) { withUpdateSource(UPDATE_SOURCE_TRANSITION, () => { root.updateSource = UPDATE_SOURCE_TRANSITION; doDeferredProcessing(root); }); } }); } export function doDeferredProcessing(root: HostFiber) { // if there is no deferred work or pending transition return const pendingTransition = getFirstTransitionToProcess(root); if (!pendingTransition) return; root.updateType = UPDATE_TYPE_DEFERRED; // reset the effect list before starting new one resetEffectProperties(root); // set the pending transition as current transition root.currentTransition = pendingTransition; pendingTransition.tryCount += 1; // $FlowFixMe: Passing root on top level component is exception root.wip = cloneCurrentFiber(root.current, root.wip, root, root); workLoop(root.wip, root); } export function doSyncProcessing(fiber: Fiber) { const { root, parent } = fiber; root.updateType = UPDATE_TYPE_SYNC; // set current transition as null for sync processing root.currentTransition = null; // reset the effect list before starting new one resetEffectProperties(root); workLoop(fiber, parent); } ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "outDir": "./lib", "allowJs": true, "checkJs": true, "target": "es5", "jsx": "preserve" }, "include": ["./src/**/*"] } ================================================ FILE: webpack.config.js ================================================ var HtmlWebpackPlugin = require('html-webpack-plugin'); var path = require('path'); module.exports = { entry: './example/index.js', mode: 'development', output: { path: path.resolve(__dirname, './dist'), filename: 'bundle.js', }, devServer: { compress: true, port: 8083, }, plugins: [ new HtmlWebpackPlugin({ template: './example/index.html', }), ], resolve: { alias: { brahmos: path.resolve(__dirname, 'src'), react: path.resolve(__dirname, 'src'), 'react-dom': path.resolve(__dirname, 'src'), }, }, module: { rules: [ { test: /\.js$/, exclude: /(node_modules|bower_components)/, use: { loader: 'babel-loader', }, }, { test: /\.s[ac]ss|\.css$/i, use: [ // Creates `style` nodes from JS strings 'style-loader', // Translates CSS into CommonJS 'css-loader', // Compiles Sass to CSS 'sass-loader', ], }, ], }, };