react-jtm-loader

0.3.2 • Public • Published

Webpack React Template Loader

A loader for Webpack that allows you to write JSX without a lot of JS.

The real draw here is for those who prefer their HTML/JSX to not commingle with their Javascript code. This is also pretty handy for React components that have multiple and/or large sections of JSX that, when separated from the JS, make visual digestion easier.

  • Create functional and class based components
  • Use simple hooks (useContext, useState, and useRef) in functional components idiomatically.
  • Create high-order functions to extend your components
  • Use 'class' and 'for' attributes instead of 'className' and 'htmlFor'

Very Cool! How do I get it?

npm install react-jtm-loader --save-dev

How do I use it?

Setup a webpack rule (in module.rules array) that looks something like this:

 {
    test: /\.jtm$/,
    use:
    [
        {
            loader: 'babel-loader',
            options: { presets: ["@babel/preset-react", "@babel/preset-env"] }
        },
        'react-jtm-loader'
    ]
}

You might also want to add '.jtm' to the resolve.extensions list.

Then you can write a JSX template file like this:

Greeting.jtm

<extender name="templates">
  <render>
    <h1 class="hello"> Hi, {props.name}! </h1>
  </render>
</extender>

And then apply to a component like this:

Greeting.js

import React from 'react';
import PropTypes from 'prop-types';
import { templates } from './Greeting.jtm';
 
class Greeting extends React.Component
{
    static propTypes =
    {
        name: PropTypes.string
    }
}
 
export default templates(Greeting);

A render method is added to the class above that looks something like this:

render()
{
    var { props, state, context, constructor: klass  } = this;

    return (<h1 className="hello"> Hi, {props.name}! </h1>);
}

Additionally, the above example could be written as a component class

Greeting.jtm

<class name="Greeting">
  <render>
    <h1 class="hello"> Hi, {props.name}! </h1>
  </render>
</class>

which would eliminate the need for Greeting.js and generate

class Greeting extends React.Component
{
    constructor(props)
    {
        super(props);
        this.state = { };
    }

    render()
    {
        var { props, state, context, constructor: klass  } = this;

        return (<h1 className="hello"> Hi, {props.name}! </h1>);
    }
}

You could also write this as a functional component.

Greeting.jtm

<sfc name="Greeting">
  <h1 class="hello"> Hi, {props.name}! </h1>
</sfc>

which would get you

function Greeting(props)
{
    return (<h1 className="hello"> Hi, {props.name}! </h1>);
}

That's about all there is to it!

What can I do in a .jtm file?

The JSX template method (.jtm) file is a custom markup file that allows for a specific set of tags.

<class>

Generates and exports a component class for stateful components.

<class name="Dialog" initState="open: false">
  <render state="open">
    <React.Fragment>
      <ModalWindow isOpen={open} onRequestClose={() => setState({ open: false })}>
        Action performed successfully!
      </ModalWindow>
      <Button onClick={() => setState({ open: true })}>Do Action</Button>
    <React.Fragment>
  </render>
</class>

The simplified example above creates a react class component that renders a ModalWindow that is initially closed. Clicking the button will open it, while clicking the ModalWindow's close button (assumed) would close it by way of its onRequestClose prop.

Exactly one nameless render jtm tag must appear inside a class tag as it will generate the render function for the component. Additional ones must be named.

<context>

Creates and exports a react context.

<context name="MyStringContext" default="'A simple string'" />
<context />

becomes

var MyStringContext = React.createContext({ string: 'A simple string' });
var DefaultContext = React.createContext();

<extender>

Generates and exports a high-order function that applies render functions to a component.

extendMe.jtm

<extender name="extend">
  <render>
    <div> Default Render { this.show('Gina', 21) }</div>
  </render>
  <render name="show" args="name, value">
    <div>
      Showing {name} and {value}.
    </div>
  </render>
</extender>

Use the above like

import React from 'react';
import { extend } from 'extendMe';
 
class MyComponent extends React.Component {}
 
export extend(MyComponent);

Now, MyComponent has the required render as well as a show function.

<import>

Imports content from elsewhere.

Use the import jtm tag just like an ES6 import statement.

<import spec="Icon" from="component-lib/Icon" />
<import spec="* as utils" from="../../lib/utils" />
<import from="react" />

becomes

import Icon from 'component-lib/Icon';
import * as utils from '../../lib/utils';
import 'react';

<render>

Generates a JSX function (must be a child of class or extender).

<render>
  <div class={state.classes}>
    <h1>Hello {props.name}</h1>
  </div>
</render>

becomes

render()
{
    var { props, state, context, constructor: klass } = this;
 
    return (
      <div class={state.classes}>
        <h1>Hello {props.name}</h1>
      </div>
    );
}

As you can see, props, state, and klass component members are made directly available to the JSX code.

You can further destructure these members as needed (omit outermost braces).

<render props="name" state="classes">
  <div class={classes}>
    <h1>Hello {name}</h1>
  </div>
</render>

The above yields

render()
{
    var { props, state, context, constructor: klass  } = this;
    var { name } = props;
    var { classes } = state;
 
    return (
      <div class={classes}>
        <h1>Hello {name}</h1>
      </div>
    );
}

You can also destructure this in the same manner by using a 'this' attribute.

By default, the name of the method created will be 'render', but you can specify a name as well as additional parameters.

<render name="content" args="classes">
  <div class={classes}>
    {props.children}
  </div>
</render>

The above transforms into

content(classes)
{
    var { props, state, context, constructor: klass  } = this;
 
    return (
      <div class={classes}>
        {props.children}
      </div>
    );
}

The 'args' attribute is specified in the same way you would define an argument list for any javascript method.

looping the template

A render template can render its JSX content multiple times (returning an array) using the loop attribute.

The value for loop must be a literal or a variable defined via an argument or other destructuring.

If the value of loop is:

  • an array then content is rendered for each array element
  • a number then content is rendered that number of times
  • a string then content is rendered for each (comma-delimited) part
  • an object then content is rendered for each key of the object
  • any other value then content is iterated once for that value

When loop is specified, three more variables are available to the JSX template content:

  • item - the current iteration item
  • index - index of the current iteration
  • array - the full array being iterated

Note that when loop is a number each item is null.

<sfc>

Generates and exports a stateless/stateful functional component.

<sfc name="ToyTracker" props="adjective" useCounter="0">
  <div>
    <div>You have {counter} {adjective} toys.</div>
    <button onClick={() => setCounter(counter + 1)}>Add One</button>
    <button onClick={() => setCounter(counter + 2)}>Add Two!</button>
  </div>
</sfc>

becomes

function ToyTracker(props)
{
    var { adjective } = props;
    var [ counter, setCounter ] = useState(0);
    return (
      <div>
        <div>You have {counter} {adjective} toys.</div>
        <button onClick={() => setCounter(counter + 1)}>Add One</button>
        <button onClick={() => setCounter(counter + 2)}>Add Two!</button>
      </div>
    );
}

As you can see, you can destructure props in the same way you can for render. You can also iterate content via loop. See the above discussion for render tag for these.

Adding any attribute to an sfc tag that starts with 'use' will generate a react state hook. For instance, useFirstName="'Randy'" will generate var [ firstName, setFirstName ] = useState('Randy'); in the component function.

You can also use refs with an sfc.

<sfc name="FocusInput" refs="input">
  <React.Fragment>
    <input ref={input} type="text" />
    <button onClick={() => inputRef.focus()}>Set The Focus</button>
  </React.Fragment>
</sfc>

yields

function FocusInput(props)
{
    var input = useRef(null), inputRef = input.current;
    return (
      <React.Fragment>
        <input ref={input} type="text" />
        <button onClick={() => inputRef.focus()}>Set The Focus</button>
      </React.Fragment>
    );
}

To access a react context with an sfc, use the contexts attribute.

<context name="Secret" />
 
<sfc name="WithContext" contexts="Secret">
  <div>
    The secret value is { secretValue }.
  </div>
</sfc>

Note here that secretValue is the value for context Secret. The variable name is formed from the name of the context (lowercasing the initial character) plus the word Value.

The generated code would be:

var Secret = React.createContext();
 
function WithContext(props)
{
    var secretValue = useContext(Secret);
    return (
      <div>
        The secret value is { secretValue }.
      </div>
    );
}

I Need A .jtm Attribute Quick-ref

Here you go.

<class>

  • name - name for the component (default: DefaultComponent)
  • extends - superclass for the component (default: React.Component)
  • initProps - default props for the component
  • initState - initial state for the component
  • contextType - react context for the component
  • bind - comma-delimited render method names to be bound to the class
  • highOrder - comma-delimited high-order functions for the component (executed in given order)

<context>

  • name - name for the context (default: DefaultContext)
  • default - default value for the context

<extender>

  • name - name for the extension function (default: templates)
  • prefix - prefix for all render method names

<import>

  • spec - what to import
  • from - from where to import

<render>

  • name - name for the render method (default: render)
  • args - parameter list for the render method
  • props - destructure component props
  • state - destructure component state
  • context - destructure component context
  • this - destructure component
  • klass - destructure component constructor
  • loop - iterate template content (via Array.map)

<sfc>

  • name - name for the functional component (default: DefaultComponent)
  • props - destructure argument props
  • this - destructure whatever 'this' might refer to
  • highOrder - comma-delimited high-order functions for the component (executed in given order)
  • loop - iterate template content (via Array.map)
  • use* - add useState hooks for the component
  • refs - comma-delimited useRef object names
  • contexts - comma-delimited useContext context names

Additional Notes

  • All jtm tags that can have a name attribute (except render) are exported from the .jtm file.
  • The last tag in a .jtm file without a specified name becomes the default export.
  • A .jtm file, which is XML, is always wrapped in a jsx-templates root tag, but this can be omitted.

Don't forget the imports!
The examples here were meant to be short and to the point. But don't forget to import the requisite react functionality in your .jtm file as necessary.

<import spec="React, { useContext, useRef, useState }" from="react" />

An easy way to auto-accomplish this (since you're already using Webpack) is with ProvidePlugin.

webpack.config.js

var { ProvidePlugin } = require('webpack');
 
module.exports =
{
    ... ,
 
    plugins:
    [
        new ProvidePlugin(
        {
            React: 'react',
            useContext: ['react', 'useContext'],
            useRef: ['react', 'useRef'],
            useState: ['react', 'useState']
        })
    ]
}

What Else?

Links

{ updates } { feedback } { license } { versioning }

Please be sure to check 'updates' when upgrading to a new version.

Finally

Happy JSX Templating!

Versions

Current Tags

  • Version
    Downloads (Last 7 Days)
    • Tag
  • 0.3.2
    0
    • latest

Version History

Package Sidebar

Install

npm i react-jtm-loader

Weekly Downloads

0

Version

0.3.2

License

MIT

Unpacked Size

28.6 kB

Total Files

6

Last publish

Collaborators

  • captison