Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/joe-sky/narrative

A compiler tool for create neater control flow tags such as <If>/<For>/<Switch> for JSX/TSX.
https://github.com/joe-sky/narrative

jsx react tsx

Last synced: 3 months ago
JSON representation

A compiler tool for create neater control flow tags such as <If>/<For>/<Switch> for JSX/TSX.

Awesome Lists containing this project

README

        

# Narrative


Travis CI Status
Codecov
License

## Packages

| Package | Badges |
| ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| [@narrative/control-flow](https://github.com/joe-sky/narrative/tree/master/packages/control-flow) | NPM Version NPM Downloads |
| [@narrative/babel-plugin-compiler](https://github.com/joe-sky/narrative/tree/master/packages/babel-plugin-compiler) | NPM Version NPM Downloads |
| [@narrative/swc-plugin-compiler](https://github.com/joe-sky/narrative/tree/master/packages/swc-plugin-compiler) | NPM Version NPM Downloads |
| [@narrative/vite-plugin-compiler](https://github.com/joe-sky/narrative/tree/master/packages/vite-plugin-compiler) | NPM Version NPM Downloads |

## Introduction

`Narrative`(abbreviated as `nt`) is a compiler tool for create neater control flow tags such as ``/``/`` for React JSX/TSX. It does so by transforming component-like control flow tags to their JavaScript counterparts:

```tsx
Hello World!;

// Compiled ↓ ↓ ↓ ↓ ↓ ↓

{
condition() ? 'Hello World!' : null;
}
```

The inspiration mainly comes from [jsx-control-statements](https://github.com/AlexGilleran/jsx-control-statements), this tool can be seen as an alternative solution with syntactic differences to `jsx-control-statements`. It also only depends on Babel(or SWC), and it's compatible with React and React Native.

> In addition, its API has also referenced the following projects:

- [React If](https://github.com/romac/react-if)
- [React Loops](https://github.com/leebyron/react-loops)
- [Solid(Control Flow)](https://www.solidjs.com/docs/latest/api#control-flow)

### Basic Overview

```js
import { useState, FC } from 'react';
import { If, ElseIf, Else, For, Empty, Switch, Case, Default } from '@narrative/control-flow';

const App: FC = () => {
const [todos, setTodos] = useState([]);

const addTodo = () => {
setTodos(todos.concat(`Item ${todos.length}`));
};

return (




    {(todo, { index }) => (
    5}>
  • {todo * 3}

  • 10}>
  • {todo * 4}



  • {todo * 5}



  • )}

  • No data






    {(item, { key }) =>
  • {item}
  • }

  • No data





1
2
3/4/5
More than 2


);
};
```

### Highlights

`Narrative` has similar or different features as `jsx-control-statements`:

- ✨ Tag names are more like native JavaScript control statements.
- 💫 More concise syntax keywords of tags.
- ⭐ Tags supports full TypeScript inference.
- ⚡ No runtime code, just need compiler.
- 🔥 Supports both Babel and SWC compilers.
- 🔧 Support syntax error prompt for Babel and SWC.

## Table of Contents

- [Examples](#examples)
- [Installation](#installation)
- [Using with Babel](#using-with-babel)
- [Using with SWC](#using-with-swc)
- [Usage](#usage)
- [If Tag](#if-tag)
- [<If>](#if)
- [<Else>](#else)
- [<ElseIf>](#elseif)
- [Function children of <If> <ElseIf> <Else>](#function-children-of-if-elseif-else)
- [Switch Tag](#switch-tag)
- [<Switch> <Case>](#switch-case)
- [Multiple values of <Case>](#multiple-values-of-case)
- [<Default>](#default)
- [Function children of <Case> <Default>](#function-children-of-case-default)
- [For Tag](#for-tag)
- [<For of>](#for-of)
- [<For in>](#for-in)
- [Loop iteration metadata](#loop-iteration-metadata)
- [<Empty>](#empty)

## Examples

### React + Vite(use Babel compiler)

narrative-react-vite-demo[https://github.com/joe-sky/narrative-react-vite-demo]

### React + Vite(use SWC compiler)

narrative-react-vite-swc-demo[https://github.com/joe-sky/narrative-react-vite-swc-demo]

## Installation

### Using with Babel

```bash
npm i @narrative/control-flow @narrative/babel-plugin-compiler
```

Configure `Babel`:

```json
{
"plugins": ["@narrative/compiler"]
}
```

### Using with SWC

```bash
npm install @narrative/control-flow @narrative/swc-plugin-compiler
```

Configure `SWC`:

```json
{
"jsc": {
"experimental": {
"plugins": [["@narrative/swc-plugin-compiler", {}]]
}
}
}
```

- If your `swc_core` version is lower than 0.8, please install the old version:

```bash
npm install @narrative/[email protected]
```

## Usage

Each JSX tags must to be imported from `@narrative/control-flow` when use:

```tsx
import { If, Else, ElseIf } from '@narrative/control-flow';

function render(no: number) {
return (

1

2


0


);
}
```

As above the usage is similar to the regular React components. Each tags and its props also support TypeScript type checking.

### If Tag

`` tag is an alternative syntax for conditional logic in JSX, it is similar to the `if statement` in JavaScript. Also supports ``, ``, and the syntax design fully supports JSX native formatting. Simple examples:

```tsx
import { If } from '@narrative/control-flow';

// simple

IfBlock

// using multiple child elements or expressions

one
{"two"}
three
four
= 5}>
five

```

Click here to view how the compiler works

```tsx
5}>

  • {todo * 2}

  • 10}>
  • {todo * 3}


  • ;

    // Compiled ↓ ↓ ↓ ↓ ↓ ↓

    {
    index > 5 ?

  • {todo * 2}
  • : index > 10 ?
  • {todo * 3}
  • : null;
    }
    ```

    #### <If>

    If only use ``, the children of `` will be returned when the value of `when prop` is true.

    | Prop Name | Prop Type | Required |
    | --------- | --------- | ------------------ |
    | when | boolean | :white_check_mark: |

    ```tsx
    import { If } from '@narrative/control-flow';

    1}>
    IfBlock1
    IfBlock2
    ;

    // Compiled ↓ ↓ ↓ ↓ ↓ ↓

    {
    no > 1 ? (
    <>
    IfBlock1
    IfBlock2
    >
    ) : null;
    }
    ```

    #### <Else>

    Only one `` can be added within a ``. If the `when prop` value of `` is false, then the children of `` will be returned:

    ```tsx
    import { If, Else } from '@narrative/control-flow';

    1}>
    IfBlock1
    IfBlock2

    IfBlock3
    IfBlock4

    ;

    // Compiled ↓ ↓ ↓ ↓ ↓ ↓

    {
    no > 1 ? (
    <>
    IfBlock1
    IfBlock2
    >
    ) : (
    <>
    IfBlock3
    IfBlock4
    >
    );
    }
    ```

    #### <ElseIf>

    Multiple `` can be added within a ``, and any one of the tag children with a `when prop` of true will be returned:

    ```tsx
    import { If, Else, ElseIf } from '@narrative/control-flow';

    10}>
    IfBlock1
    5}>
    IfBlock2

    1}>
    IfBlock3


    IfBlock4

    ;

    // Compiled ↓ ↓ ↓ ↓ ↓ ↓

    {
    no > 10 ? (
    IfBlock1
    ) : no > 5 ? (
    IfBlock2
    ) : no > 1 ? (
    IfBlock3
    ) : (
    IfBlock4
    );
    }
    ```

    #### Function children of <If> <ElseIf> <Else>

    The children of ``, ``, `` also supports a function. It can be used for logic that calculates first and then renders:

    ```tsx
    import { If } from '@narrative/control-flow';

    1}>
    {() => {
    const blockName = 'IfBlock';

    return (
    <>
    {blockName}1
    {blockName}2
    >
    );
    }}

    {() => {
    const blockName = 'ElseBlock';

    return (
    <>
    {blockName}1
    {blockName}2
    >
    );
    }}

    ;

    // Compiled ↓ ↓ ↓ ↓ ↓ ↓

    {
    no > 1
    ? (() => {
    const blockName = 'IfBlock';

    return (
    <>
    {blockName}1
    {blockName}2
    >
    );
    })()
    : (() => {
    const blockName = 'ElseBlock';

    return (
    <>
    {blockName}1
    {blockName}2
    >
    );
    })();
    }
    ```

    ### Switch Tag

    `` tag is an alternative syntax for multi branch conditional statements in JSX, it is similar to the `switch statement` in JavaScript. Also supports ``, ``. Simple examples:

    ```tsx
    import { Switch } from '@narrative/control-flow';


    1


    2


    3


    0

    ;

    // Compiled ↓ ↓ ↓ ↓ ↓ ↓

    {
    todos.length === 1 ? (
    1
    ) : todos.length === 2 ? (
    2
    ) : todos.length === 3 ? (
    3
    ) : (
    0
    );
    }
    ```

    #### <Switch> <Case>

    `` requires to set the `value prop`.

    | Prop Name | Prop Type | Required |
    | --------- | --------- | ------------------ |
    | value | any | :white_check_mark: |

    Each `` matches the `value prop of ` via `is prop`. At least one `` is required within the ``.

    | Prop Name | Prop Type | Required |
    | --------- | --------- | ------------------------------- |
    | is | any | Either `is` or `in` is required |

    Example:

    ```tsx
    import { Switch, Case } from '@narrative/control-flow';


    1


    2

    ;

    // Compiled ↓ ↓ ↓ ↓ ↓ ↓

    {
    todos.length === 1 ? 1 : todos.length === 2 ? 2 : null;
    }
    ```

    As above, the `` use `strict equality(===)` when matching.

    #### Multiple values of <Case>

    If multiple values need to be matched in one ``, the `in prop` can be used.

    | Prop Name | Prop Type | Required |
    | --------- | -------------------- | ------------------------------- |
    | in | ArrayLike<any> | Either `is` or `in` is required |

    Example:

    ```tsx
    import { Switch, Case } from '@narrative/control-flow';


    1


    2


    3/4/5

    ;

    // Compiled ↓ ↓ ↓ ↓ ↓ ↓

    {
    todos.length === 1 ? (
    1
    ) : todos.length === 2 ? (
    2
    ) : [3, 4, 5].includes(todos.length) ? (
    3/4/5
    ) : null;
    }
    ```

    #### <Default>

    `` can have one `` inside it, which will be matched to the `` when all `` do not match:

    ```tsx
    import { Switch, Case, Default } from '@narrative/control-flow';


    1


    2/3


    0

    ;

    // Compiled ↓ ↓ ↓ ↓ ↓ ↓

    {
    todos.length === 1 ? 1 : [2, 3].includes(todos.length) ? 2/3 : 0;
    }
    ```

    #### Function children of <Case> <Default>

    The children of ``, `` also supports a function. It can be used for logic that calculates first and then renders:

    ```tsx
    import { Switch, Case, Default } from '@narrative/control-flow';


    {() => {
    const blockName = 'CaseBlock';

    return (
    <>
    {blockName}1
    {blockName}2
    >
    );
    }}


    {() => {
    const blockName = 'DefaultBlock';

    return (
    <>
    {blockName}1
    {blockName}2
    >
    );
    }}

    ;

    // Compiled ↓ ↓ ↓ ↓ ↓ ↓

    {
    todos.length === 1
    ? (() => {
    const blockName = 'CaseBlock';

    return (
    <>
    {blockName}1
    {blockName}2
    >
    );
    })()
    : (() => {
    const blockName = 'DefaultBlock';

    return (
    <>
    {blockName}1
    {blockName}2
    >
    );
    })();
    }
    ```

    ### For Tag

    `` tag is an alternative syntax for loops logic in JSX. Example:

    ```tsx
    import { For } from '@narrative/control-flow';

    {(todo, { index }) =>

  • {todo}
  • }

  • No data


  • ;

    // Compiled ↓ ↓ ↓ ↓ ↓ ↓

    {
    (__arr => {
    if (__arr?.length) {
    return __arr.map((todo, index) => {todo}, this);
    }
    return

  • No data
  • ;
    })(todos);
    }
    ```

    #### <For of>

    `` loops is similar to the `for of statement` in JavaScript, the `of prop` accepts Arrays and Array-likes.

    | Prop Name | Prop Type | Required |
    | --------- | -------------------- | ------------------ |
    | of | ArrayLike<any> | :white_check_mark: |

    The loop callback function is in children of ``, example:

    ```tsx
    import { For } from '@narrative/control-flow';

    {(todo, { index }, arr) =>

  • {todo}
  • };

    // Compiled ↓ ↓ ↓ ↓ ↓ ↓

    {
    todos?.map?.((todo, index, arr) => {
    return {todo};
    }, this) || null;
    }
    ```

    As above the callback function parameters:

    | Parameter order | Type | Description | Required |
    | --------------- | --------------------------------------------------- | --------------------------- | ------------------ |
    | first | type of Array items | each items of Array | :white_check_mark: |
    | second | [Loop iteration metadata](#loop-iteration-metadata) | mainly using index of Array | |
    | third | type of Array | looping Array variable | |

    #### <For in>

    `` loops is similar to the `for in statement` in JavaScript, the `in prop` accepts an Object.

    | Prop Name | Prop Type | Required |
    | --------- | ---------------- | ------------------ |
    | in | Record | :white_check_mark: |

    The loop callback function is in children of ``, example:

    ```tsx
    import { For } from '@narrative/control-flow';

    {(item, { key }, obj) => {item}};

    // Compiled ↓ ↓ ↓ ↓ ↓ ↓

    {
    (__obj => {
    const __keys = __obj ? Object.keys(__obj) : [];

    if (__keys.length) {
    return __keys.map(key => {
    const item = __obj[key];
    const obj = __obj;
    return {item};
    }, this);
    }
    })({ a: 1, b: 2, c: 3 });
    }
    ```

    As above the callback function parameters:

    | Parameter order | Type | Description | Required |
    | --------------- | --------------------------------------------------- | ---------------------------- | ------------------ |
    | first | type of Object values | each values of source object | :white_check_mark: |
    | second | [Loop iteration metadata](#loop-iteration-metadata) | mainly using key and keys | |
    | third | type of Object | looping Object variable | |

    #### Loop iteration metadata

    Access additional information about each iteration by the second callback argument:

    - `index`: A number from 0 to the length of the Arrays or Objects.
    - `key`: The key for this item in Objects, same as `index` for Arrays.
    - `keys`: The keys Arrays for Objects.

    ```tsx
    import { For, If } from '@narrative/control-flow';

    {(item, { key, index, keys }) => (

    {item}
    2 && index > 1}>{index}

    )}
    ;

    // Compiled ↓ ↓ ↓ ↓ ↓ ↓

    {
    (__obj => {
    const __keys = __obj ? Object.keys(__obj) : [];

    if (__keys.length) {
    return __keys.map((key, index) => {
    const item = __obj[key];
    const keys = __keys;
    return (

    {item}
    {keys.length > 2 && index > 1 ? index : null}

    );
    }, this);
    }
    })({ a: 1, b: 2, c: 3 });
    }
    ```

    #### <Empty>

    A common pattern when rendering a collection is to render a special case when the collection is empty. Optionally provide a `` to handle this case for both `` and `` loops. `` should be set in the children of ``, Example:

    ```tsx
    import { For, Empty } from '@narrative/control-flow';

    const emptyObj = {};

    {(item, { key }) => {item}}
    No Data
    ;

    // Compiled ↓ ↓ ↓ ↓ ↓ ↓

    {
    (__obj => {
    const __keys = __obj ? Object.keys(__obj) : [];

    if (__keys.length) {
    return __keys.map(key => {
    const item = __obj[key];
    return {item};
    }, this);
    }

    return 'No Data';
    })(emptyObj);
    }
    ```

    ## The Origin of Name

    🤖 `RX-9 Narrative Gundam`, ready to launch!


    Narrative

    ## License

    MIT