0.2.0 β€’ Published 1 year ago

babel-plugin-direactive v0.2.0

Weekly downloads
-
License
MIT
Repository
-
Last release
1 year ago

Use directives in React like Vue.

NPM version


πŸ€”Motivation

After moving from the Vue stack to React, I still like the expression of the Vue template. Using this plugin can improve the expression of the React template just like the directive in Vue.

  • Before
function Component() {
  return (
    <section>
      {
        isLoadong ? <div>Loading...</div>
          : error ? <div>{error}</div>
          : shopList.length > 0 ? (
              <ul>
                {
                  shopList.map(shop => (
                    <li key={shop.id}>
                      <h5>{shop.name}</h5>
                      <ul style={{ display: isShowProducts ? 'block' : 'none' }}>
                        {
                          shop.productList.map(product => (
                            <li key={product.id}>
                              <h6>{product.name}</h6>
                            </li>
                          ))
                        }
                      </ul>
                    </li>
                  ))
                }
              </ul>
          ) : <div>Empty</div>
      }
    </section>
  )
}
  • After
function Component() {
  return (
    <section>
      <div r-if={isLoadong}>Loading...</div>
      <div r-else-if={error}>{error}</div>
      <ul r-else-if={shopList.length > 0}>
        <li r-for="shop in shopList" r-key="shop.id">
          <h6>{shop.name}</h6>
          <ul r-show={isShowProducts}>
            <li r-for="product in shop.productList" r-key="product.id">
              <h6>{product.name}</h6>
            </li>
          </ul>
        </li>
      </ul>
      <div r-else>Empty</div>
    </section>
  )
}

Where to add babel-plugin-direactive

Which directives are supported

  • r-if
  • r-else-if
  • r-else
  • r-for/r-key
  • r-show
  • r-html
  • r-text
  • r-pre
  • r-model

Install

npm install babel-plugin-direactive -D

Via .babelrc or babel-loader.

{
  "plugins": ["babel-plugin-direactive"]
}

Usage

All directive attribute values are parsed as expression, except for r-for directive (which use special expressions)

r-if

<div r-if="foo"/>
// equivalent:
<div r-if={foo}/>

<div r-if="foo === 1"/>
// equivalent:
<div r-if={foo === 1}/>

Refer to https://vuejs.org/api/built-in-directives.html#v-if

r-else-if/r-else

<div r-if="foo">foo</div>
<div r-else-if="bar">bar</div>
<div r-else>baz</div>

r-for/r-key

<ul>
  <li r-for"item, index of arr" r-key="index">
    {item}
  </li>
</ul>

// object
<ul>
  <li r-for"val, key, index of obj" r-key="key">
    {key}: {val}
  </li>
</ul>

Note: Use r-key instead of key

Refer to https://vuejs.org/api/built-in-directives.html#v-for

r-show

<div r-show="foo"/>

Refer to https://vuejs.org/api/built-in-directives.html#v-show

r-html

const html = "<div onclick='javascript:alert(1)'>alert</div>";
<div r-html="html"/>

Refer to https://vuejs.org/api/built-in-directives.html#v-html

r-text

<div r-text="variable"/>
<div r-text={variable}/>
<div r-text="'raw string'"/>
<div r-text={'raw string'}/>

Refer to https://vuejs.org/api/built-in-directives.html#v-text

r-pre

There is a requirement that r-pre content conform to JSX syntax.

// not work
<div r-pre>{this will not be compiled}</div>

// work
<div r-pre>{content}</div>
<div r-pre>{this}</div>

// preserve line wrap
<pre r-pre>
  {
    content
  }
</pre>

Refer to https://vuejs.org/api/built-in-directives.html#v-pre

r-model

  • text
function Component() {
  const value = useState(0);
  return <input r-model={value}/>
}

// Read value
function Component() {
  const [value, setValue] = useState(0);
  return (
    <>
      <p>value: {value}</p>
      <input r-model={[value, setValue]}/>
    </>
  )
}

Note: The r-model directive does not support modifiers.

  • textarea
function Component() {
  const value = useState(0);
  return <textarea r-model={value}/>
}
  • checkbox
// The value is true if checkbox is checked, false otherwise
function Component() {
  const checkbox = useState(true);
  return (
    <>
      <input type="checkbox" r-model={checkbox} />
      <label>{checkbox[0]}</label>
    </>
  )
}

// You can also specify the checked value and the unchecked value
function Component() {
  const checkbox = useState(0);
  return (
    <>
      <input type="checkbox" true-value={1} false-value={0} r-model={checkbox} />
      <label>{checkbox[0]}</label>
    </>
  )
}

// Group
function Component() {
  const checkedNames = useState([]);
  return (
    <>
      <div>Checked names: {checkedNames[0]}</div>
      <input type="checkbox" value="Jack" r-model="checkedNames">
      <input type="checkbox" value="John" r-model="checkedNames">
      <input type="checkbox" value="Mike" r-model="checkedNames">
    </>
  )
}
  • radio
function Component() {
  const picked = useState('One');
  return (
    <>
      <div>Picked: {picked[0]}</div>
      <input type="radio" value="One" r-model="picked" />
      <input type="radio" value="Two" r-model="picked" />
    </>
  )
}
  • select
function Component() {
  const selected = useState('A');
  return (
    <>
      <div>Selected: {selected[0]}</div>
      <select r-model="selected">
        <option disabled value="">Please select one</option>
        <option value="A">A</option>
        <option value="B">B</option>
        <option value="C">C</option>
      </select>
    </>
  )
}

// Multiple select (bound to array):
function Component() {
  const selected = useState('A');
  return (
    <>
      <div>Selected: {selected[0]}</div>
      <select r-model="selected" multiple>
        <option value="A">A</option>
        <option value="B">B</option>
        <option value="C">C</option>
      </select>
    </>
  )
}

Note: The option value can only be a string literal.

Refer to https://vuejs.org/api/built-in-directives.html#v-model

  • component
function Parent() {
  const state = useState();
  return <Child r-model={state}/>
}

// The component will receive two props: value and onChange
function Child(props) {
  return <div
    r-text={props.value}
    onClick={props.onChange('new value')}
  />
}

How it works

The plugin performs the following transformations at compile time.

For a clearer description, it can be understood as follows, and the implementation is in the source code.

r-if/r-else-if/r-else

<div r-if="foo">foo</div>
<div r-else-if="bar">bar</div>
<div r-else>baz</div>

      ↓ ↓ ↓ ↓ ↓ ↓

foo 
  ? <div>foo</div>
  : bar
    ? <div>bar</div>
    : <div>baz</div>

r-for/r-key

<ul>
  <li r-for"item, index of arr" r-key="index">
    {item}
  </li>
</ul>

      ↓ ↓ ↓ ↓ ↓ ↓

<ul>
  {
    arr.map((item, index) => <li key={index}>{item}</li>)
  }
</ul>

r-show

<div r-show="foo"/>

      ↓ ↓ ↓ ↓ ↓ ↓

<div style={ foo ? {} : { display: 'none' } }/>

r-html

const html = "<div onclick='javascript:alert(1)'>alert</div>";
<div r-html="html"/>

      ↓ ↓ ↓ ↓ ↓ ↓

<div dangerouslySetInnerHTML={{ __html: html }}/>

r-text

<div r-text="variable"/>
<div r-text={variable}/>
<div r-text="'raw string'"/>
<div r-text={'raw string'}/>

      ↓ ↓ ↓ ↓ ↓ ↓

<div>{variable}</div>
<div>{variable}</div>
<div>{'raw string'}</div>
<div>{'raw string'}</div>

r-pre

<div r-pre>{content}</div>
<div r-pre>{this}</div>
<pre r-pre>
  {
    content
  }
</pre>

      ↓ ↓ ↓ ↓ ↓ ↓

<div>{`{content}`}</div>
<div>{`{this}`}</div>
<pre>{`
  {
    content
  }
`}</pre>

r-model

  • text
<input r-model={value}/>

      ↓ ↓ ↓ ↓ ↓ ↓

<input value={value[0]} onChange={e => value[1](e.target.value)}/>
  • textarea
<textarea r-model={value}/>

      ↓ ↓ ↓ ↓ ↓ ↓

<textarea value={value[0]} onChange={e => value[1](e.target.value)}/>
  • checkbox
<input type="checkbox" r-model={checkbox} />
<input type="checkbox" true-value={1} false-value={0} r-model={checkbox} />

      ↓ ↓ ↓ ↓ ↓ ↓

<input type="checkbox" checked={checkbox[0] === true} onChange={e => checkbox[1](e.target.checked ? true : false)} />
<input type="checkbox" checked={checkbox[0] === 1} onChange={e => checkbox[1](e.target.checked ? 1 : 0)} />
  • radio
<input type="radio" value="One" r-model="picked" />

      ↓ ↓ ↓ ↓ ↓ ↓

<input type="radio" value="One" checked={picked[0] === 'One'} onChange={e => picked[1]('One')}/>
  • select
<select r-model="selected">
  <option value="A">A</option>
  <option value="B">B</option>
  <option value="C">C</option>
</select>

      ↓ ↓ ↓ ↓ ↓ ↓

<select value={selected[0]} onChange={e => selected[1](e.target.value)}>
  <option value="A">A</option>
  <option value="B">B</option>
  <option value="C">C</option>
</select>
  • component
<Child r-model={state}/>

      ↓ ↓ ↓ ↓ ↓ ↓

<Child value={state[0]} onChange={state[1]}/>