Skip to content

redexp/react-separate-template

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

49 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

React separate template

Build Status

  1. Solution description
    1. @render
    2. jsx-tpl
    3. jsx-class
    4. jsx-instance
  2. TodoMVC example
  3. Installation
  4. Usage from command line
  5. Usage from code
  6. Additional features
  7. Gulp plugin
  8. Grunt plugin

First problem of react is HTML inside our JS files and there is no way to make them separate.

Solution description

So I want to take out HTML from this class to separate file

var MenuExample = React.createClass({
    getInitialState: function() {
        return {focused: 0};
    },
    clicked: function(index) {
        this.setState({focused: index});
    },
    render: function() {
        var self = this;

        return (
            <div>
                <ul>
                    {this.props.items.map(function(m, index) {
                        var style = '';

                        if (self.state.focused == index) {
                            style = 'focused';
                        }

                        return (<li className={style}>{m}</li>);
                    })}
                </ul>

                <p>Selected: {this.props.items[this.state.focused]}</p>
            </div>
        );
    }
});

But if we just take out everything after return it's not what I want. I need absolutely clear HTML so I can write my CSS and see results without running whole app. I want to se template like this

<div>
    <ul>
        <li class={style}>{m}</li>
    </ul>

    <p>Selected: {this.props.items[this.state.focused]}</p>
</div>

Okay, first of all we need to put this.props.items.map(...) in ul. I thought, annotation is good enough to make link to some function like this

@render

<div>
    <ul>
        <!-- @render list -->
    </ul>

    <p>Selected: {this.props.items[this.state.focused]}</p>
</div>

But where should be that list function? What if it will be right after our return, where was this template?

var MenuExample = React.createClass({

    //...

    render: function() {
        var self = this;

        return {
            list: function () {
                this.props.items.map(function(m, index) {
                    var style = '';

                    if (self.state.focused == index) {
                        style = 'focused';
                    }

                    return <li className={style}>{m}</li>;
                })
            }
        };
    }
});

But wait, not that again, HTML in code, maybe we can take it out, but it should be in same template file. Maybe we will make some annotation which will be replaced with HTML (with some unique id) from our template?

jsx-tpl

var MenuExample = React.createClass({

    //...

    render: function() {
        var self = this;

        return {
            list: function () {
                this.props.items.map(function(m, index) {
                    var style = '';

                    if (self.state.focused == index) {
                        style = 'focused';
                    }

                    return "@jsx-tpl item";
                })
            }
        };
    }
});

Annotation not in comment because it is a value, not just some meta information. Lets call attribute with this unique id just like annotation.

<div>
    <ul>
        <!-- @render list -->
        <li jsx-tpl="item" class={style}>{m}</li>
    </ul>

    <p>Selected: {this.props.items[this.state.focused]}</p>
</div>

Lets say that all tags with attribute jsx-tpl will be detached from template and can be used only in classes to replace @jsx-tpl annotations. So basically they can be anywhere in template.

jsx-class

Last thing, what if we want many templates in one html file? Lets add jsx-class attribute with displayName value of react class to element in template to join them

<div jsx-class="Menu">
    <ul>
        <!-- @render list -->
        <li jsx-tpl="item" class={style}>{m}</li>
    </ul>

    <p>Selected: {this.props.items[this.state.focused]}</p>
</div>

<div jsx-class="SomeOtherComponent">
    ...
</div>

So we should add to our classes filed displayName:

var MenuExample = React.createClass({
    displayName: 'Menu',
    //...
    render: function () {
        //...
    }
});

var x = React.createClass({
    displayName: 'SomeOtherComponent',
    //...
    render: function () {
        //...
    }
});

jsx-instance

If you define jsx-class in another jsx-class or jsx-tpl then this definition will be replaced to the end of body, but what if you need instance of this class right in this place? Just add jsx-instance="true" to it. All attributes with curly brackets will be with instance.

Example

<div>
    <ul jsx-class="List">
        <li jsx-class="Item" jsx-instance="true" user="{user}" class="row" data-id="1">
            <h3>{user.name}</h3>
        </li>
    </ul>
</div>

Will be converted to

<div>
    <ul jsx-class="List">
        <Item jsx-tpl="item" user="{user}" />
    </ul>
</div>

<li jsx-class="Item" class="row" data-id="1">
    <h3>{user.name}</h3>
</li>

TodoMVC Example

Here original implementation of TodoMVC project on React and here same project but with separated html examples/todomvc. You can see that each component has it template. But wait, checkout one combined template components/index.html for all components. You can use it to compile all components. You can just open it and see how your app looks like without running js code. How cool is that?

Installation

npm install react-st

Usage from command line

Just run react-st -j test/js/menu.js. It will take file test/js/menu.js, join with test/js/menu.html and save to test/js/menu.jsx

To see all options run react-st -h

Usage from code

Example

var convert = require('react-st');

convert(jsString, htmlString, function (err, jsxString) {
  // check err
  // save jsxString to file or whatever you want
});

Additional features

  • All class attributes will renamed to className
  • All jsx-spread="someVar" attributes will be converted to Spread Attribute {...someVar}
  • I added syntax like this class="item {style}" and it will be converted to className={"item " + style}. Helpful for styling html without running js code

Notice

All attributes with curly brackets will be converted to react jsx version. If you will write something like this data-bind="attr: {active: isActive}" it will be converted to data-bind={'attr: ' + active: isActive}, so be careful.

Gulp plugin

See https://github.com/redexp/gulp-react-st

Grunt plugin

See https://github.com/TheWolfNL/grunt-react-separate-templates

Contribute!

I will be glad if you have any suggestions, just create an issue.