{"id":26951870,"url":"https://github.com/uqbar-project/njsx","last_synced_at":"2025-08-26T07:04:41.424Z","repository":{"id":19024626,"uuid":"84905516","full_name":"uqbar-project/njsx","owner":"uqbar-project","description":"A customizable and declarative interface for creating React and React Native components without JSX syntax.","archived":false,"fork":false,"pushed_at":"2023-01-27T04:28:12.000Z","size":76,"stargazers_count":35,"open_issues_count":8,"forks_count":4,"subscribers_count":18,"default_branch":"master","last_synced_at":"2025-08-20T00:29:21.541Z","etag":null,"topics":["builder","customizable","dsl","functional","js","jsx","react-native","reactjs","syntactic-sugar","ts","tsx"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/uqbar-project.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-03-14T04:29:13.000Z","updated_at":"2025-02-13T22:53:18.000Z","dependencies_parsed_at":"2023-02-15T05:16:47.717Z","dependency_job_id":null,"html_url":"https://github.com/uqbar-project/njsx","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/uqbar-project/njsx","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/uqbar-project%2Fnjsx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/uqbar-project%2Fnjsx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/uqbar-project%2Fnjsx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/uqbar-project%2Fnjsx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/uqbar-project","download_url":"https://codeload.github.com/uqbar-project/njsx/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/uqbar-project%2Fnjsx/sbom","scorecard":{"id":911628,"data":{"date":"2025-08-11","repo":{"name":"github.com/uqbar-project/njsx","commit":"40b8818cc22ec05a7c703d4750bdd76f92a13033"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.9,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Code-Review","score":0,"reason":"Found 1/21 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":9,"reason":"license file detected","details":["Info: project has a license file: LICENSE.md:0","Warn: project license file does not contain an FSF or OSI license."],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 10 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"12 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-4q6p-r6v2-jvc5","Warn: Project is vulnerable to: GHSA-2pr6-76vf-7546","Warn: Project is vulnerable to: GHSA-8j8c-7jfh-h6hx","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-vh95-rmgr-6w4m","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-r683-j2x4-v87g","Warn: Project is vulnerable to: GHSA-hj48-42vr-x3v9","Warn: Project is vulnerable to: GHSA-g6ww-v8xp-vmwg","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-fhg7-m89q-25r3"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-24T19:25:37.810Z","repository_id":19024626,"created_at":"2025-08-24T19:25:37.810Z","updated_at":"2025-08-24T19:25:37.810Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272186243,"owners_count":24888339,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-26T02:00:07.904Z","response_time":60,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["builder","customizable","dsl","functional","js","jsx","react-native","reactjs","syntactic-sugar","ts","tsx"],"created_at":"2025-04-03T00:17:56.000Z","updated_at":"2025-08-26T07:04:41.396Z","avatar_url":"https://github.com/uqbar-project.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# No-JSX\n\n[![Build Status](https://travis-ci.org/uqbar-project/njsx.svg?branch=master)](https://travis-ci.org/uqbar-project/njsx)\n[![npm version](https://badge.fury.io/js/njsx.svg)](https://badge.fury.io/js/njsx)\n\nA pure function based interface for creating [React](https://facebook.github.io/react/) and [React Native](https://facebook.github.io/react-native/) components without *JSX* tags.\n\nIf you love *React* but don't quite like the embeded *HTML* tags this library may be what you are looking for. Construct your components with code only in a clean, declarative way.\n\n```js\nconst myView = () =\u003e\n  div.app(\n    div.header(\n      img({src: logo, alt:'logo'}),\n      h2('Welcome to NJSX')\n    )\n  )()\n```\n-------------------------------------------------\n\n#### Table of Content\n\n- [Installation](#installation)\n- [Usage](#usage)\n  - [Getting a Builder](#getting-a-builder)\n    - [Default Component Builders](#default-component-builders)\n    - [Third-Party Component Builders](#third-party-component-builders)\n  - [Creating Elements](#creating-elements)\n  - [Refining Builders](#refining-builders)\n    - [Builder Arguments](#builder-arguments)\n    - [Dynamic Selectors](#dynamic-selectors)\n  - [Argument Transformation](#argument-transformation)\n  - [Point-free](#point-free)\n- [Working with older versions](#working-with-older-versions)\n- [Contributions](#contributions)\n- [License](#license)\n\n-------------------------------------------------\n\n## Installation\n\n*NJSX* is available on [npm](https://www.npmjs.com), just pick between the *React* and *React Native* flavours and add it to your project's dependencies.\n\n**For React Projects:**\n```bash\nnpm install njsx-react --save\n```\n\n**For React Native Projects:**\n```bash\nnpm install njsx-react-native --save\n```\n\n\n## Usage\n\n*NJSX* is super easy to use: It's all about **Builder Functions**.\n\nYou can use *Builders* to to cleanly instantiate *React* and *React Native* elements, or further refine your component configuration just by applying them.\n\n\n### Getting a Builder\n\n#### Default Component Builders\n\n*NJSX* provides *Builder Functions* for all the default *React* and *React Native* components. Just import whatever element you need from the `react` or `react-native` modules and you are ready to go:\n\n```js\n// React project\nimport {div, p} from 'njsx-react'\n\n// React Native project\nimport {View, Text} from 'njsx-react-native'\n```\n\n#### Third-Party Component Builders\n\nNJSX is not just for default components! You can get a builder for **any** component just by wrapping it with the `njsx` adapter.\n\n```js\n// This is NJSX core. Both njsx-react and njsx-react-native use it to define their builders.\nimport njsx from 'njsx'\nimport {SomeThirdPartyComponent} from 'someLibrary'\n\nconst SomeFunctionalComponent = (props) =\u003e ...\nclass SomeStatefulComponent extends React.Component {\n  render() { ... }\n}\n\n// These are all valid Component Builders.\nconst someComponent = njsx(SomeComponent)            \nconst someFunctionalComponent = njsx(SomeFunctionalComponent)\nconst someStatefulComponent = njsx(SomeFunctionalComponent)\n\n// You can even use a string as component type (although it's not recommended):\nconst aDivBuilder = njsx('div')\n```\n\n### Creating Elements\n\nEach *NJSX* builder, once **applied with no arguments**, will return a **ReactElement** just as if you had used the component inside a *JSX* tag:\n\n```jsx\nimport {div} from 'njsx-react'\n\n// These two lines are equivalent.\n\u003cdiv\u003e\u003c/div\u003e\ndiv()\n```\n\nThis means that *JSX* and *NJSX* elements are completely interchangeable. You can use components created from builders as children for *JSX*'s tags, or refine a builder with tag shaped children, so you can try *NJSX* on any react based project and integrate it gradually.\n\n### Refining Builders\n\nOf course, an empty element is not that useful, so how do you customize it?\n\nWhen a *Builder* is applied with **one or more arguments**, these will be used to configure the building component. Refining a *Builder* this way returns another ***Builder***, so you can keep refining your component any number of times.\n\n```jsx\nimport {p} from 'njsx-react'\n\np('some text')\np('some', ' ', 'text')\np('some')(' ')('text')\np(['some', ' ', 'text'])\n\n// All these lines build the same:\n\u003cp\u003esome text\u003c/p\u003e\n```\n\nIt's important to note that refining a builder causes **no side effects or state changes at all**. This means you can safely reuse *Builders*, or partially refine one and pass it forward.\n\n#### Builder Arguments\n\n*Builders* will get refined in a different way, depending on what arguments you apply them with:\n\n  - `Basic Objects` are treated as **Component Properties**. Refining a builder with a second set of properties will result in the merge of both, favoring the later in case of repetition.\n  \n    ```jsx\n    img({src: path, onClick: cb})\n    img({src: path}, {onClick: cb})\n    img({src: thisWillBeLost, onClick: cb})({src: path})\n\n    // All these lines build the same:\n    \u003cimg src={path} onClick:{cb}\u003e\u003c/img\u003e\n    ```\n\n\n  - `Strings`, `Numbers`, `React Elements` and even other `Builders` will become **Component Children**.\n\n    ```jsx\n    div(\n      div('the answer is ', 42)  // \u003c- No need for building it.\n    )\n\n    // This line builds:\n    \u003cdiv\u003e\u003cdiv\u003ethe answer is 42\u003c/div\u003e\u003c/div\u003e\n    ```\n\n    Notice that, since *Builders* can be children too, most of the time you won't be needing to apply them with no arguments to instantiate elements.\n\n\n  - `null`, `undefined` and `Booleans` will be ignored. This allows for a clean way to conditionally set properties and children using `\u0026\u0026` and `||`.\n\n    ```jsx\n    div(null)\n    div(undefined)\n    div(false \u0026\u0026 \"this won't show\")\n\n    //All these lines the same:\n    \u003cdiv/\u003e\n    ```\n\n  - `Arrays` of any valid argument will be handled as a sequence of refinements.\n\n    ```jsx\n    const guards = ['Nobby', 'Colon', 'Carrot']\n    \n    ul(guards.map(guard =\u003e li(guard)))\n    ul(guards.map(li))\n\n    //All these lines the same:\n    \u003cul\u003e{guards.map(guard =\u003e \u003cli\u003e{guard}\u003c/li\u003e)}\u003c/ul\u003e\n    ```\n\n\n  - Finally, you can also pass a `Refinement Function`, which should take the previous *Component Properties* (including the `children` field) and return the next one.\n\n    ```jsx\n    const myRefinement = (src, text) =\u003e (prev) =\u003e\n      {...prev, {src, children: text} }\n    img(myRefinement(foo, bar))\n\n    // This line builds:\n    \u003cimg src={foo}\u003ebar\u003c/img\u003e\n    ```\n\nTo wrap it all, any unsuported argument application will raise a `TypeError`.\n\n\n#### Dynamic Selectors\n\nYou can also refine a *Builder* by accessing any keyword as if it was a property. A common use for this is to treat the keyword as a `className`, so you can add classes to components by just naming them:\n\n```jsx\np.highlighted.small(\"Nice!\")\np['highlighted']['small'](\"Nice!\")\np['highlighted small'](\"Nice!\")\np(\"Nice!\").highlighted['.small']\n\n//All these lines build the same:\n\u003cp className=\"highlighted small\"\u003eNice!\u003c/p\u003e\n```\n\nTreating these selectors as class names is the default behavior of *NJSX*, but you can change it to whatever you want by changing the `NJSXConfig.  dynamicSelectorHandler` setting:\n\n```jsx\nimport { NJSXConfig } from 'njsx'\n\n// You can set any function that receives the key and returns a valid argument.\nNJSXConfig.dynamicSelectorHandler = (key: string) =\u003e key\n\ndiv.foo.bar\n// This would yield\n\u003cdiv\u003efoobar\u003c/div\u003e\n\n\n// That means you could also return a refining function!\nNJSXConfig.dynamicSelectorHandler = (id: string) =\u003e (prev) =\u003e\n  {...prev, id}\n\ndiv.baz\n// This would yield\n\u003cdiv id=\"baz\"/\u003e\n\n\n// You can also disable the whole thing by setting it to undefined.\nNJSXConfig.dynamicSelectorHandler = undefined\n```\n\n\nNotice that this feature can only be used on [environments that support *ES6's Proxy*](https://kangax.github.io/compat-table/es6/#test-Proxy) so, sadly, it's not available on *React-Native* projects.\n\n\n## Argument Transformation\n\nYou don't like the way arguments are being handled? No problem! You can customize the way *NJSX's Builders* interpret arguments to fine tune it to your needs.\n\nThe `NJSXConfig` object can be used to specify **Argument Transformations**, which are just functions that take each argument and return whatever you want that argument to be. These functions are automatically called each time a *Builder* is applied.\n\n```jsx\nimport { NJSXConfig } from 'njsx'\nimport {p} from 'njsx-react'\n\nconst translations = {\n  \"this should be translated\": \"ook\"\n}\n\nNJSXConfig.argumentTransformations.push( arg =\u003e\n  typeof arg === 'string' \u0026\u0026 arg.startsWith('!')\n    ? translations[arg]\n    : arg\n)\n\np(\"!this should be translated\")\n\n// This build:\n\u003cp\u003eook\u003c/p\u003e\n```\n\nPlease take into account that **all transformations are reduced on every argument**, so don't overdo it and mind the order.\n\n*NJSX* comes with some of these transformations set up by default:\n\n  - In *React* projects, **Strings starting with a dot** will be interpreted as a *classNames*:\n\n    ```jsx\n    div('.foo .bar')(\n      'Some content'\n    )\n\n    // This builds:\n    \u003cdiv className=\"foo bar\"\u003eSome content\u003c/div\u003e\n    ```\n\n  - In *React-Native* projects, **StyleSheet** arguments are interpreted as *styles* (Just import `StyleSheet` from `njsx-react-native` instead of `react-native`).\n\n    ```jsx\n    import {StyleSheet, View, Text} from 'njsx-react-native'\n\n    // Same StyleSheet interface\n    StyleSheet.create({\n      container: { /* ...your regular react-native styles... */ }\n      description: { /* ...your regular react-native styles... */ }\n    })\n\n    View(styles.container)(\n      Text(style.description)(\"These are styled!\")\n    )\n    ```\n\nIf you rather all your arguments to just be interpreted as they are, you can disable this feature by setting the `NJSXConfig.argumentTransformations` to an empty array.\n\n## Point-free\n\nThink point-free composition in your render function is a `pipe` dream? Think again, you can use `njsx` to compose components in a point-free style to help with the readability of deeply nested react components:\n\n```jsx\n\u003cProvider store={store}\u003e\n  \u003cPersistGate loading={null} persistor={persistor}\u003e\n    \u003cBrowserRouter\u003e\n      \u003cRoute path=\"/\" component={App} /\u003e\n    \u003c/BrowserRouter\u003e\n  \u003c/PersistGate\u003e\n\u003c/Provider\u003e\n```\n\nBecomes:\n\n```js\nimport { compose } from 'rambda'\n\ncompose(\n  Provider({ store }),\n  PersistGate({ loading: null, persistor }),\n  BrowserRouter,\n  Route\n)({ path: '/', component: App })()\n```\n\nPlease note that `compose` and `pipe` functions vary in implementation and not all will work with `njsx`, for example, `lodash/fp` seems to have issues at the moment, while `rambda` is working without issue.\n\n## Working with older versions\n\nIf you are working with an older release this documentation might not be of any use to you. We follow the [semantic versioning standard](https://semver.org/) so any difference on the Major version will probably imply some incompatibilities. Please refer to [your version's branch](https://github.com/uqbar-project/njsx/releases) README file.\n\n\n## Contributions\n\nPlease report any bugs, requests or ideas on [the issues section of this repository](https://github.com/uqbar-project/njsx/issues) and we will try to see to it as soon as possible.\nPull requests are always welcome! Just try to keep them small and clean.\n\n\n## License\n\nThis code is open source software licensed under the [ISC License](https://opensource.org/licenses/ISC) by [The Uqbar Foundation](http://www.uqbar-project.org/). Feel free to use it accordingly.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fuqbar-project%2Fnjsx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fuqbar-project%2Fnjsx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fuqbar-project%2Fnjsx/lists"}