├── .editorconfig
├── .gitattributes
├── .gitignore
├── .jshintrc
├── .npmignore
├── .travis.yml
├── LICENSE.txt
├── README.md
├── index.js
├── package.json
└── test.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent coding styles between different editors and IDEs
2 | # http://editorconfig.org
3 |
4 | root = true
5 |
6 | [*]
7 |
8 | # change these settings to your own preference
9 | indent_style = space
10 | indent_size = 2
11 |
12 | # we recommend you to keep these unchanged
13 | end_of_line = lf
14 | charset = utf-8
15 | trim_trailing_whitespace = true
16 | insert_final_newline = true
17 |
18 | [*.md]
19 | trim_trailing_whitespace = false
20 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # These attributes affect how the contents stored in the repository are copied
2 | # to the working tree files when commands such as git checkout and git merge run.
3 | # http://git-scm.com/docs/gitattributes
4 |
5 | * text=auto
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Git uses this file to determine which files and directories to ignore
2 | # https://help.github.com/articles/ignoring-files
3 |
4 | # Node.js
5 | node_modules
6 | npm-debug.log
7 | tmp
8 |
9 | # NPM
10 | *.tgz
11 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true,
3 | "esnext": true,
4 | "bitwise": true,
5 | "camelcase": true,
6 | "curly": true,
7 | "immed": true,
8 | "newcap": true,
9 | "noarg": true,
10 | "undef": true,
11 | "unused": "vars",
12 | "strict": true,
13 | "indent": 2
14 | }
15 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | *.tgz
2 | .idea
3 | .editorconfig
4 | .gitattributes
5 | .jshintrc
6 | .npmignore
7 | .travis.yml
8 | gulpfile.js
9 | test.js
10 | SampleComponent.jsx
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '0.10'
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Konstantin Tarkus (@koistya)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [gulp](http://gulpjs.com)-render [](http://travis-ci.org/koistya/gulp-render) [](https://david-dm.org/koistya/gulp-render) [](https://gratipay.com/koistya) [](https://gitter.im/kriasoft/react-starter-kit)
2 |
3 | > Pre-render [React](https://facebook.github.io/react/) components at compile time.
4 |
5 | ## How to Install
6 |
7 | [](https://www.npmjs.org/package/gulp-render)
8 |
9 | ```sh
10 | $ npm install gulp-render --save-dev
11 | ```
12 |
13 | ## How to Use
14 |
15 | #### Example 1:
16 |
17 | ```javascript
18 | var gulp = require('gulp');
19 | var render = require('gulp-render');
20 |
21 | gulp.task('default', function() {
22 | return gulp.src('src/pages/**/*.jsx')
23 | .pipe(render({template: 'src/pages/_template.html'}))
24 | .pipe(gulp.dest('build'));
25 | });
26 | ```
27 |
28 | #### Example 2:
29 |
30 | ```javascript
31 | var gulp = require('gulp');
32 | var render = require('gulp-render');
33 |
34 | gulp.task('default', function() {
35 | return gulp.src('src/pages/**/*.jsx')
36 | .pipe(render({
37 | template:
38 | '' +
39 | '
<%=title%>' +
40 | '<%=body%>',
41 | harmony: false,
42 | data: {title: 'Page Title'}
43 | }))
44 | .pipe(gulp.dest('build'));
45 | });
46 | ```
47 |
48 | #### React Component Sample (`src/pages/SomePage.jsx`)
49 |
50 | ```javascript
51 | var React = require('react');
52 | var DefaultLayout = require('../layouts/DefaultLayout.jsx');
53 |
54 | var SomePage = React.createClass({
55 | statics: {
56 | layout: DefaultLayout
57 | },
58 | render() {
59 | return (
60 |
61 |
React Component Sample
62 |
Lorem ipsum dolor sit amet.
63 |
64 | );
65 | }
66 | });
67 |
68 | module.exports = SomePage;
69 | ```
70 |
71 | ## API
72 |
73 | #### `render(options)`
74 |
75 | option | values | default
76 | ---------------|------------------------------------------------------------------------|--------
77 | `template` | [Lo-Dash template](http://lodash.com/docs#template) string or filename | `null`
78 | `harmony` | `true`: enable ES6 features | `true`
79 | `stripTypes` | `true`: enable [Flow](http://flowtype.org) type annotations | `true`
80 | `hyphenate` | `true`: SomePage.jsx -> some-page.html | `true`
81 | `staticMarkup` | `true`: HTML output will not have `data-react-*` attributes | `false`
82 | `data ` | E.g. `{title: 'Hello'}` or `function(file) { ... }` | `object` or `function`
83 |
84 | ## Related Projects
85 |
86 | [React.js Starter Kit](https://github.com/kriasoft/react-starter-kit) -
87 | a skeleton for an isomorphic web application (SPA)
88 |
89 | ## License
90 |
91 | The MIT License (MIT) @ Konstantin Tarkus ([@koistya](https://twitter.com/koistya))
92 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * gulp-render
3 | * https://github.com/koistya/gulp-render
4 | *
5 | * Copyright (c) 2014 Konstantin Tarkus
6 | * Licensed under the MIT license
7 | */
8 |
9 | 'use strict';
10 |
11 | var through = require('through2');
12 | var gutil = require('gulp-util');
13 | var path = require('path');
14 | var fs = require('fs');
15 | var _ = require('lodash');
16 | var React = require('react');
17 | var ReactTools = require('react-tools');
18 | var hyphenate = require('react/lib/hyphenate');
19 | var template = _.template;
20 | var PluginError = gutil.PluginError;
21 | var Module = module.constructor;
22 |
23 | // Constants
24 | var PLUGIN_NAME = 'gulp-render';
25 |
26 | /**
27 | * Append @jsx pragma to JSX files
28 | */
29 | function appendJsxPragma(filename, contents) {
30 | return filename.match(/\.jsx$/) || filename.match(/[\-\.]react\.js$/) ?
31 | '/**@jsx React.DOM*/' + contents : contents;
32 | }
33 |
34 | /**
35 | * Check if Page component has a layout property; and if yes, wrap the page
36 | * into the specified layout, then render to a string.
37 | */
38 | function renderToString(page) {
39 | var layout = null, child = null, props = {};
40 | while ((layout = page.type.layout || (page.defaultProps && page.defaultProps.layout))) {
41 | child = React.createElement(page, props, child);
42 | _.extend(props, page.defaultProps);
43 | React.renderToString(React.createElement(page, props, child));
44 | page = layout;
45 | }
46 | return React.renderToString(React.createElement(page, props, child));
47 | }
48 |
49 | /**
50 | * Just produce static markup without data-react-* attributes
51 | * http://facebook.github.io/react/docs/top-level-api.html#react.rendertostaticmarkup
52 | */
53 | function renderToStaticMarkup(page) {
54 | return React.renderToStaticMarkup(React.createElement(page));
55 | }
56 |
57 | // Plugin level function (dealing with files)
58 | function Plugin(options) {
59 |
60 | options = options || {};
61 |
62 | var reactOptions = {
63 | harmony: typeof options.harmony === 'undefined' ? true : options.harmony,
64 | stripTypes: typeof options.stripTypes === 'undefined' ? true : options.stripTypes
65 | };
66 |
67 | if (options.template && options.template.indexOf('<') === -1) {
68 | options.template = fs.readFileSync(options.template, {encoding: 'utf8'});
69 | }
70 |
71 | var originalJsTransform = require.extensions['.js'];
72 |
73 | var reactTransform = function(module, filename) {
74 | if (filename.indexOf('node_modules') === -1) {
75 | var src = fs.readFileSync(filename, {encoding: 'utf8'});
76 | src = appendJsxPragma(filename, src);
77 | src = ReactTools.transform(src, reactOptions);
78 | module._compile(src, filename);
79 | } else {
80 | originalJsTransform(module, filename);
81 | }
82 | };
83 |
84 | require.extensions['.js'] = reactTransform;
85 | require.extensions['.jsx'] = reactTransform;
86 |
87 | // Creates a stream through which each file will pass
88 | var stream = through.obj(function(file, enc, cb) {
89 |
90 | if (!file.isNull()) {
91 |
92 | if (file.isStream()) {
93 | this.emit('error', new PluginError(PLUGIN_NAME, 'Streams are not supported!'));
94 | return cb();
95 | }
96 |
97 | if (file.isBuffer()) {
98 |
99 | try {
100 | var contents = file.contents.toString('utf8');
101 | contents = appendJsxPragma(file.path, contents);
102 | contents = ReactTools.transform(contents, reactOptions);
103 | var m = new Module();
104 | m.id = file.path;
105 | m.filename = file.path;
106 | m.paths = module.paths.slice(1);
107 | m._compile(contents, file.path);
108 | var Component = m.exports;
109 | var markup = options.staticMarkup ? renderToStaticMarkup(Component) : renderToString(Component);
110 |
111 | if (options.template) {
112 | var data = _.extend({}, (typeof(options.data) == 'function' ? options.data(file) : options.data));
113 | data.body = markup;
114 |
115 | // Set default values to avoid null-reference exceptions
116 | data.title = data.title || '';
117 | data.description = data.description || '';
118 | data.keywords = data.keywords || '';
119 |
120 | markup = template(options.template, data);
121 | }
122 |
123 | file.contents = new Buffer(markup);
124 | var filename = gutil.replaceExtension(file.path, '.html');
125 |
126 | if (typeof options.hyphenate === 'undefined' || options.hyphenate) {
127 | filename = hyphenate(path.basename(filename));
128 | filename = filename.lastIndexOf('-', 0) === 0 ? filename.substring(1) : filename;
129 | filename = path.join(path.dirname(file.path), filename);
130 | }
131 |
132 | file.path = filename;
133 | } catch (err) {
134 | this.emit('error', new PluginError(PLUGIN_NAME, err));
135 | return cb();
136 | }
137 | }
138 | }
139 |
140 | // Make sure the file goes through the next gulp plugin
141 | this.push(file);
142 | // Tell the stream engine that we are done with this file
143 | return cb();
144 | });
145 |
146 | // Return the file stream
147 | return stream;
148 | }
149 |
150 | module.exports = Plugin;
151 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gulp-render",
3 | "description": "Pre-render React components at compile time. E.g. SomePage.jsx -> some-page.html",
4 | "version": "0.2.2",
5 | "homepage": "https://github.com/koistya/gulp-render",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/koistya/gulp-render.git"
9 | },
10 | "bugs": {
11 | "url": "https://github.com/koistya/gulp-render/issues"
12 | },
13 | "author": "Konstantin Tarkus (@koistya)",
14 | "licenses": {
15 | "type": "MIT",
16 | "url": "https://github.com/koistya/gulp-render/raw/master/LICENSE.txt"
17 | },
18 | "keywords": [
19 | "gulpplugin",
20 | "react",
21 | "jsx",
22 | "render",
23 | "rendering",
24 | "javascript",
25 | "es6",
26 | "harmony",
27 | "compiler",
28 | "transpiler"
29 | ],
30 | "main": "index.js",
31 | "dependencies": {
32 | "gulp-util": "^3.0.1",
33 | "lodash": "^2.4.1",
34 | "react": "^0.12.1",
35 | "react-tools": "^0.12.1",
36 | "through2": "^0.6.3"
37 | },
38 | "devDependencies": {
39 | "jshint": "^2.5.10",
40 | "mocha": "^2.0.1"
41 | },
42 | "scripts": {
43 | "test": "jshint index.js test.js && mocha"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/test.js:
--------------------------------------------------------------------------------
1 | /*
2 | * gulp-render
3 | * https://github.com/koistya/gulp-render
4 | *
5 | * Copyright (c) 2014 Konstantin Tarkus
6 | * Licensed under the MIT license
7 | */
8 |
9 | /* global require, Buffer, it */
10 |
11 | 'use strict';
12 |
13 | var assert = require('assert');
14 | var gutil = require('gulp-util');
15 | var render = require('./');
16 |
17 | it('Should render a simple React component', function(cb) {
18 |
19 | var stream = render();
20 |
21 | stream.on('data', function(file) {
22 | var contents = file.contents.toString('utf8');
23 | assert(contents.indexOf('Hello world!') != -1);
24 | cb();
25 | });
26 |
27 | stream.write(new gutil.File({
28 | path: 'SampleComponent.jsx',
29 | cwd: __dirname,
30 | contents: new Buffer(
31 | 'var React = require("./node_modules/react"); ' +
32 | 'var HelloMessage = React.createClass({' +
33 | ' render: function() {return React.DOM.div(null, "Hello world!");}' +
34 | '}); '+
35 | 'module.exports = HelloMessage;'
36 | )
37 | }));
38 |
39 | });
40 |
41 | it('Should render a simple React component with a template', function(cb) {
42 |
43 | var stream = render({
44 | template: '<%=title%><%=body%>',
45 | data: { title: 'Title123' }
46 | });
47 |
48 | stream.on('data', function(file) {
49 | var contents = file.contents.toString('utf8');
50 | assert(contents.indexOf('Hello world!') != -1);
51 | assert(contents.indexOf('Title123') != -1);
52 | cb();
53 | });
54 |
55 | stream.write(new gutil.File({
56 | path: 'SampleComponent.jsx',
57 | cwd: __dirname,
58 | contents: new Buffer(
59 | 'var React = require("./node_modules/react"); ' +
60 | 'var HelloMessage = React.createClass({' +
61 | ' render: function() {return React.DOM.div(null, "Hello world!");}' +
62 | '}); '+
63 | 'module.exports = HelloMessage;'
64 | )
65 | }));
66 |
67 | });
68 |
69 | it('Should render a simple React component as static markup', function(cb) {
70 |
71 | var stream = render({
72 | staticMarkup: true
73 | });
74 |
75 | stream.on('data', function(file) {
76 | var contents = file.contents.toString('utf8');
77 | assert(contents.indexOf('data-react') === -1);
78 | cb();
79 | });
80 |
81 | stream.write(new gutil.File({
82 | path: 'SampleComponent.jsx',
83 | cwd: __dirname,
84 | contents: new Buffer(
85 | 'var React = require("./node_modules/react"); ' +
86 | 'var HelloMessage = React.createClass({' +
87 | ' render: function() {return React.DOM.div(null, "Hello world!");}' +
88 | '}); '+
89 | 'module.exports = HelloMessage;'
90 | )
91 | }));
92 |
93 | });
94 |
95 | it('Should render a simple React component with a template and data function', function(cb) {
96 |
97 | var stream = render({
98 | template: '<%=title%><%=body%>',
99 | data: function(file) {
100 | return { title: 'Test123' + file.path };
101 | }
102 | });
103 |
104 | stream.on('data', function(file) {
105 | var contents = file.contents.toString('utf8');
106 | assert(contents.indexOf('Hello world!') != -1);
107 | assert(contents.indexOf('Test123SampleComponent.jsx') != -1);
108 | cb();
109 | });
110 |
111 | stream.write(new gutil.File({
112 | path: 'SampleComponent.jsx',
113 | cwd: __dirname,
114 | contents: new Buffer(
115 | 'var React = require("./node_modules/react"); ' +
116 | 'var HelloMessage = React.createClass({' +
117 | ' render: function() {return React.DOM.div(null, "Hello world!");}' +
118 | '}); '+
119 | 'module.exports = HelloMessage;'
120 | )
121 | }));
122 |
123 | });
124 |
125 | it('Should render a React component with a layout defined in default props', function(cb) {
126 |
127 | var stream = render();
128 |
129 | stream.on('data', function(file) {
130 | var contents = file.contents.toString('utf8');
131 | assert(contents.indexOf('Layout') != -1);
132 | assert(contents.indexOf('Test') != -1);
133 | assert(contents.indexOf('Hello world!') != -1);
134 | cb();
135 | });
136 |
137 | stream.write(new gutil.File({
138 | path: 'SampleComponent.jsx',
139 | cwd: __dirname,
140 | contents: new Buffer(
141 | 'var React = require("./node_modules/react"); ' +
142 | 'var Layout = React.createClass({' +
143 | ' render: function() {return React.DOM.div(null, ["Layout", this.props.title, this.props.children]);}' +
144 | '}); '+
145 | 'var HelloMessage = React.createClass({' +
146 | ' getDefaultProps() {' +
147 | ' return {' +
148 | ' "title": "Test",' +
149 | ' "layout": Layout' +
150 | ' }' +
151 | ' },' +
152 | ' render: function() {return React.DOM.div(null, "Hello world!");}' +
153 | '}); '+
154 | 'module.exports = HelloMessage;'
155 | )
156 | }));
157 |
158 | });
159 |
160 | it('Should render a React component with a layout defined in statics', function(cb) {
161 |
162 | var stream = render();
163 |
164 | stream.on('data', function(file) {
165 | var contents = file.contents.toString('utf8');
166 | assert(contents.indexOf('Layout') != -1);
167 | assert(contents.indexOf('Hello world!') != -1);
168 | cb();
169 | });
170 |
171 | stream.write(new gutil.File({
172 | path: 'SampleComponent.jsx',
173 | cwd: __dirname,
174 | contents: new Buffer(
175 | 'var React = require("./node_modules/react"); ' +
176 | 'var Layout = React.createClass({' +
177 | ' render: function() {return React.DOM.div(null, ["Layout", this.props.children]);}' +
178 | '}); '+
179 | 'var HelloMessage = React.createClass({' +
180 | ' statics: {' +
181 | ' "layout": Layout' +
182 | ' },' +
183 | ' render: function() {return React.DOM.div(null, "Hello world!");}' +
184 | '}); '+
185 | 'module.exports = HelloMessage;'
186 | )
187 | }));
188 |
189 | });
190 |
--------------------------------------------------------------------------------