A Webpack loader allowing imports of HTML templates as if they were React components.
This project is a Proof of Concept that it is possible to write React pure functional components like HTML5 templates, almost as a Web Component.
./click-me-view.jsx.html
<template default>
<button use-props="{{ props.buttonProps }}">
Clicked {{ props.clicks }} time(s)
</button>
</template>
./click-me-container.jsx
import React, { Component } from 'react';
// Import the HTML template as if it was a React component.
import ClickMeView from './click-me-view';
export default class ClickMeContainer extends Component {
constructor(props) {
super(props);
this.state = { clicks: 0 };
this.buttonProps = { onMouseDown: this.handleMouseDown.bind(this) };
}
handleMouseDown(e) {
e.preventDefault();
this.setState({ clicks: this.state.clicks + 1 });
}
render() {
return (
<ClickMeView
buttonProps={ this.buttonProps }
clicks={ this.state.clicks }
/>
);
}
}
Add the react-html-template-loader to your webpack.config.js:
{
module: {
loaders: [
{
test: /\.jsx\.html$/,
exclude: /node_modules/,
loader: 'babel!react-html-template'
}
]
},
resolve: {
extensions: [ '.jsx', '.jsx.html' ]
}
}
- Default and named imports/exports,
- Multiple template definitions in the same file,
- Explicit conditional and loop rendering,
- Props spreading,
- CSS modules.
npm install --save-dev react-html-template-loader
Why this POC?
React provides a great developing experience, you finally have a strong integration between the JavaScript code and the template syntax, it feels natural to write.
But this merge isn't that good for designers who just know enough HTML and, depending on the requirements, it can be a disqualifying criteria for React.
Thanks to the pure functional components and the Presentational and Container pattern, most components are simply templates having data as input and some UI as output. What if those pure functional templates could simply be written in HTML to be easily created and modified by designers?
The purpose of this POC is to show that it is possible to use HTML components as a React pure functional component.
react-html-template-loader reconcile developers and designers. It is a Webpack loader compiling HTML templates into pure functional React components.
Some demos can be found under the demo/
folder, to launch one type in a
console:
npm run demo -- <demo-path>
For example:
npm run demo -- demo/todo-list
Import the default export of a file.
Usage
<link rel="import" href id />
Attributes
rel
: Must be set toimport
for this kind of relation,href
: Path of the file to import,id
: Name to use to reference the default export of the file.
Example
<link rel="import" href="path/to/component" id="my-component" />
Is equivalent in ES2015 to:
import MyComponent from 'path/to/component';
Import an export by its name. The <link>
tag for a named import must be
child of an other <link>
tag having a href
attribute.
Usage
<link rel="import" href>
<link rel="import" [name] id />
</link>
Attributes
rel
: Must be set toimport
for this kind of relation,href
: Path of the file to import,name
(Optional): Name of the component to import, can be omitted if it is the same asid
,id
: Name to use to reference the export.
Example
<link rel="import" href="path/to/component">
<link rel="import" id="component-one" />
<link rel="import" name="component-two" id="component-alias" />
</link>
Is equivalent in ES2015 to:
import {
ComponentOne,
ComponentTwo as ComponentAlias
} from 'path/to/component';
Import the default and some named exports from the same file.
Usage
<link rel="import" href id>
<link rel="import" [name] id />
</link>
Attributes
- See default imports and named imports.
Example
<link rel="import" href="path/to/component" id="my-component">
<link rel="import" id="component-one" />
<link rel="import" name="component-two" id="component-alias" />
</link>
Is equivalent in ES2015 to:
import MyComponent, {
ComponentOne,
ComponentTwo as ComponentAlias
} from 'path/to/component';
Import a global stylesheet.
Usage
<link rel="stylesheet" href />
Attributes
rel
: Must be set tostylesheet
for this kind of relation,href
: Path of the file to import.
Example
<link rel="stylesheet" href="./global-style" />
Is equivalent in ES2015 to:
import './global-style';
Import a stylesheet and name it.
Usage
<link rel="stylesheet" href id />
Attributes
rel
: Must be set tostylesheet
for this kind of relation,href
: Path of the file to import,id
: Value to use to reference the stylesheet.
Example
<link rel="stylesheet" href="./style" id="{{ style }}" />
Is equivalent in ES2015 to:
import style from './style';
It can be used this way:
<div class="{{ style.myClassName }}" />
A template is an HTML tag containing a single root child.
Each file must contain at most one default template. A default template is the main template of the file.
Usage
<template default [id]>
<!-- Content -->
</template>
Attributes
default
: Flag the default template,id
(Optional): Tag name to use to reference this template. Also used to set thedisplayName
of the template for debug purpose.
Example
<template default id="hello-world">
<div>Hello World</div>
</template>
Is equivalent in React to:
export default function HelloWorld() {
return (
<div>Hello World</div>
);
}
HelloWorld.displayName = 'HelloWorld';
A named template is simply a template with an id
attribute, which means it
can be used by referencing its name. All named templates will be exported under
their given name.
Usage
<template id>
<!-- Content -->
</template>
Attributes
id
: Tag name to use to reference this template. Also used to set thedisplayName
of the template for debug purpose.
Example
<template id="named-template">
<!-- ... -->
</template>
<template default>
<!-- ... -->
<named-template />
<!-- ... -->
</template>
Is equivalent in React to:
export function NamedTemplate(props) {
return (
// ...
);
}
NamedTemplate.displayName = 'NamedTemplate';
export default function(props) {
return (
// ...
<NamedTemplate />
// ...
);
}
A loop will render its content for each element in the array. The render
tag
can only have one child. When looping over an array, an id
tag must be set on
each child tag.
Usage
<render for-each as>
<!-- Content -->
</render>
Attributes
for-each
: Array of data,as
: Name of the variable to use for each element.
Example
<template default>
<div class="users">
<render for-each="{{ props.users }}" as="{{ user }}">
<div key="{{ user.id }}">{{ user.name }}</div>
</render>
</div>
</template>
Is equivalent in React to:
export default function(props) {
return (
<div className="users">
{ props.users.map(user => (
<div key={ user.id }>
{{ user.name }}
</div>
)) }
</div>
);
}
A conditional will render its content depending on a condition. The render
tag
can only have one child.
Usage
<render if>
<!-- Content -->
</render>
Attributes
if
: Condition to fulfill for the content to be rendered.
Example
<template default>
<div class="user">
<render if="{{ props.user }}">
<div>{{ props.user.name }}</div>
</render>
</div>
</template>
Is equivalent in React to:
export default function(props) {
return (
<div className="user">
{ props.user && <div>{{ props.user.name }}</div> }
</div>
);
}
Props spreading is used to simplify the template so the focus can be kept on the UI.
Usage
<any-tag use-props>
<!-- Content -->
</any-tag>
Attributes
use-props
: Variable that will be spread in the corresponding tag.
Example
Instead of writing:
<template default>
<button
on-mouse-down="{{ props.handleMouseDown }}"
on-key-down="{{ props.handleKeyDown }}"
on-focus="{{ props.handleFocus }}"
on-blur="{{ props.handleBlur }}"
>
Clicked {{ props.clicks }} time(s)
</button>
</template>
Just write:
<template default>
<button use-props="{{ props.buttonProps }}">
Clicked {{ props.clicks }} time(s)
</button>
</template>
Which is equivalent in React to:
export default function(props) {
return (
<button { ...props.buttonProps }>
Clicked { props.clicks } time(s)
</button>
);
}
MIT.