README
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
, anduseRef
) 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 itemindex
- index of the current iterationarray
- 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 (exceptrender
) 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!