├── .bowerrc ├── .gitignore ├── README.md ├── gulpfile.js ├── package.json ├── src ├── assets │ ├── apple-touch-icon-precomposed.png │ └── favicon.ico ├── data │ ├── data.js │ ├── index.json │ └── site.json ├── js │ ├── components │ │ ├── EmailInput.jsx │ │ ├── Hello.jsx │ │ ├── LoginForm.jsx │ │ ├── feature-list.jsx │ │ └── feature.jsx │ ├── main.jsx │ └── search.jsx ├── less │ ├── _one.less │ ├── _two.less │ ├── feature.less │ └── main.less └── templates │ ├── _layout.twig │ ├── index.twig │ └── modules │ └── _feature.twig └── webpack.config.js /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "src/assets/vendor/" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | src/assets/vendor -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | gulp-react-webpack-twig-boilerplate 2 | ================== 3 | 4 | Base repo for building static pages compiled using Twig.js (ported from PHP Twig), with static asset compilation, and React JSX compiling and bundling with Webpack. -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var data = require('gulp-data'); 3 | var twig = require('gulp-twig-with-data'); 4 | var less = require('gulp-less'); 5 | var plumber = require('gulp-plumber'); 6 | var prettify = require('gulp-html-prettify'); 7 | var webpack = require("webpack"); 8 | var browserify = require('gulp-browserify'); 9 | var concat = require('gulp-concat'); 10 | var webpack = require('gulp-webpack'); 11 | 12 | var connect = require('gulp-connect'); 13 | var open = require("gulp-open"); 14 | var port = process.env.port || 3031; 15 | 16 | gulp.task('webpack', function() { 17 | return gulp.src('./src/js/*.jsx') 18 | .pipe(webpack(require('./webpack.config.js'))) 19 | .pipe(gulp.dest('dist/assets/js')); 20 | }); 21 | 22 | gulp.task('compile', function() { 23 | return gulp.src(['./src/templates/*.twig', '!**/templates/**/_*.twig']) 24 | .pipe(plumber()) 25 | .pipe(data(require('./src/data/data.js'))) 26 | .pipe(twig()) 27 | .pipe(prettify({ 28 | indent_char: ' ', 29 | indent_size: 2 30 | })) 31 | .pipe(gulp.dest('./dist')); 32 | }); 33 | 34 | gulp.task('less', function() { 35 | return gulp.src(['./src/less/*.less', '!./src/less/_*.less']) 36 | .pipe(plumber()) 37 | .pipe(less()) 38 | .pipe(gulp.dest('./dist/assets/css')) 39 | }); 40 | 41 | gulp.task('assets', function() { 42 | return gulp.src('./src/assets/**/*') 43 | .pipe(plumber()) 44 | .pipe(gulp.dest('./dist/assets/')); 45 | }); 46 | 47 | gulp.task('open', function() { 48 | gulp.src('./dist/index.html') 49 | .pipe(open('', { 50 | url: 'http://localhost:' + port, 51 | })); 52 | }); 53 | 54 | // live reload server 55 | gulp.task('connect', function() { 56 | connect.server({ 57 | root: 'dist', 58 | port: port, 59 | livereload: true 60 | }); 61 | }); 62 | 63 | // live reload css 64 | gulp.task('reload:css', function() { 65 | gulp.src('./dist/assets/**/*.css') 66 | .pipe(connect.reload()); 67 | }); 68 | 69 | // live reload js 70 | gulp.task('reload:js', function() { 71 | gulp.src('./dist/assets/**/*.js') 72 | .pipe(connect.reload()); 73 | }); 74 | 75 | // live reload html 76 | gulp.task('reload:html', function() { 77 | gulp.src('./dist/*.html') 78 | .pipe(connect.reload()); 79 | }); 80 | 81 | // watch files for live reload 82 | gulp.task('watch', function() { 83 | gulp.watch('./dist/assets/**/*.css', ['reload:css']); 84 | gulp.watch('./dist/assets/**/*.js', ['reload:js']); 85 | gulp.watch('./dist/*.html', ['reload:html']); 86 | gulp.watch('./src/less/*.less', ['less']); 87 | gulp.watch(['./src/**/*.twig', './src/**/*.json'], ['compile']); 88 | gulp.watch('./src/js/**/*.jsx', ['webpack']); 89 | }); 90 | 91 | gulp.task('build', [ 92 | 'less', 93 | 'assets', 94 | 'compile', 95 | 'webpack' 96 | ]); 97 | 98 | gulp.task('serve', [ 99 | 'build', 100 | 'connect', 101 | 'open', 102 | 'watch' 103 | ]); 104 | 105 | gulp.task('default', ['build']); 106 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "landingpage-static", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/colynb/landingpage-static.git" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/colynb/landingpage-static/issues" 17 | }, 18 | "homepage": "https://github.com/colynb/landingpage-static", 19 | "dependencies": { 20 | "gulp": "^3.8.10", 21 | "gulp-twig-with-data": "0.0.1" 22 | }, 23 | "devDependencies": { 24 | "gulp-browserify": "^0.5.1", 25 | "gulp-concat": "^2.4.3", 26 | "gulp-connect": "^2.2.0", 27 | "gulp-data": "^1.1.1", 28 | "gulp-html-prettify": "0.0.1", 29 | "gulp-less": "^2.0.1", 30 | "gulp-open": "^0.3.1", 31 | "gulp-plumber": "^0.6.6", 32 | "gulp-util": "^3.0.2", 33 | "gulp-webpack": "^1.1.2", 34 | "jsx-loader": "^0.12.2", 35 | "lodash": "^2.4.1", 36 | "react": "^0.12.2", 37 | "rimraf": "^2.2.8", 38 | "webpack": "^1.4.15" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/assets/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colynb/gulp-react-webpack-twig-boilerplate/716e61032025b792b5cfe056f26eadb7d2e3e8e6/src/assets/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colynb/gulp-react-webpack-twig-boilerplate/716e61032025b792b5cfe056f26eadb7d2e3e8e6/src/assets/favicon.ico -------------------------------------------------------------------------------- /src/data/data.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var path = require('path'); 3 | var fs = require('fs'); 4 | 5 | module.exports = function(file) { 6 | var pagePath = path.join(__dirname, path.basename(file.path, '.twig')) 7 | return _.assign( 8 | JSON.parse(fs.readFileSync(pagePath + '.json')), 9 | JSON.parse(fs.readFileSync(path.join(__dirname, 'site.json')))); 10 | } 11 | -------------------------------------------------------------------------------- /src/data/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "heading": "Hello, Landing Page Boilerplate!", 3 | "subhead": "This is a template for a simple marketing or informational website. It includes a large callout called a jumbotron and three supporting pieces of content. Use it as a starting point to create something more unique." 4 | } 5 | -------------------------------------------------------------------------------- /src/data/site.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Colyn's Landing Page Boilerplate" 3 | } 4 | -------------------------------------------------------------------------------- /src/js/components/EmailInput.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 3 | var React = require('react'); 4 | 5 | var EmailInput = React.createClass({ 6 | 7 | render: function() { 8 | return ( 9 | 10 | ); 11 | } 12 | 13 | }); 14 | 15 | module.exports = EmailInput; -------------------------------------------------------------------------------- /src/js/components/Hello.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 3 | var React = require('react'); 4 | 5 | var Hello = React.createClass({ 6 | 7 | render: function() { 8 | return ( 9 |
Hello {this.props.message}
10 | ); 11 | } 12 | 13 | }); 14 | 15 | module.exports = Hello; 16 | -------------------------------------------------------------------------------- /src/js/components/LoginForm.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 3 | var React = require('react'); 4 | var $ = require('jquery'); 5 | var Bacon = require('bacon'); 6 | 7 | var EmailInput = require('./EmailInput.jsx'); 8 | 9 | var LoginForm = React.createClass({ 10 | 11 | doLogin: function(loginInfo) { 12 | return loginInfo; 13 | }, 14 | 15 | componentDidMount: function() { 16 | var $buttonInput = $(this.refs.buttonInput.getDOMNode()); 17 | var $emailInput = $(this.refs.emailInput.getDOMNode()); 18 | var $passwordInput = $(this.refs.passwordInput.getDOMNode()); 19 | var buttonStream = $buttonInput.asEventStream("click"); 20 | var emailInputStream = $emailInput.asEventStream("keyup"); 21 | var passwordInputStream = $passwordInput.asEventStream("keyup"); 22 | var passwordEnterStream = passwordInputStream.filter(function(e) { 23 | return e.keyCode == 13; 24 | } 25 | ); 26 | var loginStream = Bacon.mergeAll(buttonStream, emailInputStream, passwordInputStream, passwordEnterStream) 27 | .map(function(){ 28 | return { 29 | email: $emailInput.val(), 30 | password: $passwordInput.val() 31 | }; 32 | }) 33 | .filter(function(loginInfo){ 34 | return loginInfo.email && loginInfo.password; 35 | }) 36 | .flatMapLatest(this.doLogin) 37 | .log(); 38 | }, 39 | 40 | render: function() { 41 | return ( 42 |
43 |
44 | 45 |
46 |
47 | 48 |
49 | 50 |
51 | ); 52 | } 53 | 54 | }); 55 | 56 | module.exports = LoginForm; -------------------------------------------------------------------------------- /src/js/components/feature-list.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Feature = require('./feature.jsx'); 3 | 4 | var FeatureList = React.createClass({ 5 | 6 | render: function() { 7 | return ( 8 |
9 | 10 | 11 | 12 |
13 | ); 14 | } 15 | 16 | }); 17 | 18 | module.exports = FeatureList; -------------------------------------------------------------------------------- /src/js/components/feature.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | 3 | var Feature = React.createClass({ 4 | 5 | render: function() { 6 | return ( 7 |
8 |

Heading

9 |

Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui.

10 |

View details »

11 |
12 | ); 13 | } 14 | 15 | }); 16 | 17 | module.exports = Feature; -------------------------------------------------------------------------------- /src/js/main.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 3 | var React = require('react'); 4 | var FeatureList = require('./components/feature-list.jsx'); 5 | var LoginForm = require('./components/LoginForm.jsx'); 6 | 7 | React.renderComponent( 8 | , 9 | document.getElementById('features') 10 | ); 11 | 12 | // React.renderComponent( 13 | // , 14 | // document.getElementById('loginForm') 15 | // ); 16 | 17 | -------------------------------------------------------------------------------- /src/js/search.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 3 | var React = require('react'), 4 | Hello = require('./components/Hello.jsx'); 5 | 6 | React.renderComponent( 7 | , 8 | document.getElementById('search') 9 | ); 10 | -------------------------------------------------------------------------------- /src/less/_one.less: -------------------------------------------------------------------------------- 1 | .one { 2 | color: green; 3 | } 4 | -------------------------------------------------------------------------------- /src/less/_two.less: -------------------------------------------------------------------------------- 1 | .two { 2 | color: red; 3 | } 4 | -------------------------------------------------------------------------------- /src/less/feature.less: -------------------------------------------------------------------------------- 1 | .feature { 2 | color: blue; 3 | } 4 | -------------------------------------------------------------------------------- /src/less/main.less: -------------------------------------------------------------------------------- 1 | @import url(http://fonts.googleapis.com/css?family=Bree+Serif); 2 | @import '../assets/vendor/bootstrap/less/bootstrap.less'; 3 | html { 4 | position: relative; 5 | min-height: 100%; 6 | } 7 | h1 { 8 | font-family: 'Bree Serif', serif; 9 | } 10 | .btn-primary { 11 | color: #ffffff; 12 | background-color: #333; 13 | border-color: #333; 14 | &:hover { 15 | background-color: #666; 16 | border-color: #666; 17 | } 18 | } 19 | .btn { 20 | border-radius: 2px; 21 | } 22 | .footer { 23 | color: white; 24 | bottom: 0; 25 | width: 100%; 26 | /* Set the fixed height of the footer here */ 27 | 28 | background-color: #333; 29 | padding: 50px; 30 | margin: 15px 0 0 0; 31 | } 32 | 33 | .lp-feature-section { 34 | &:nth-child(even) { 35 | background: #eee; 36 | } 37 | padding: 20px 0 40px 0; 38 | border-bottom: solid 1px #eee; 39 | .lp-feature-section-head { 40 | margin-bottom: 50px; 41 | font-family: 'Bree Serif', serif; 42 | } 43 | .lp-feature-section-image { 44 | text-align: center; 45 | } 46 | .lp-feature-section-copy { 47 | font-size: 24px; 48 | color: #666; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/templates/_layout.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {% block meta %} 9 | 10 | 11 | 12 | 13 | 14 | 15 | {% endblock %} 16 | 17 | 18 | 19 | 22 | 54 | 55 | {% block page %}{% endblock %} 56 | 57 |
58 |
59 |

© Company 2014

60 |
61 |
62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /src/templates/index.twig: -------------------------------------------------------------------------------- 1 | {# index.twig #} 2 | {% extends "_layout.twig" %} 3 | 4 | {% block page %} 5 |
6 |
7 |

{{ heading }}

8 |

{{ subhead }}

9 |

10 | Learn more » 11 |

12 |
13 |
14 | 15 | 16 |
17 |
18 |

Awesome Feature

19 |
20 |
21 |
22 | Lorem ipsum dolor sit amet, movet scribentur eu mea, his ex dicant mucius dictas, nam in etiam corpora. Duo constituam delicatissimi no, vel ex illud augue consetetur 23 |
24 |
25 |
26 |
27 | 28 |
29 |
30 |
31 |
32 |
33 | 34 |
35 |
36 |

Another Awesome Feature

37 |
38 |
39 |
40 | 41 |
42 |
43 |
44 |
45 | Lorem ipsum dolor sit amet, movet scribentur eu mea, his ex dicant mucius dictas, nam in etiam corpora. Duo constituam delicatissimi no, vel ex illud augue consetetur 46 |
47 |
48 |
49 |
50 |
51 | 52 |
53 |
54 |

React.js Content

55 | 56 |
57 |
58 |
59 | 60 | {% endblock %} -------------------------------------------------------------------------------- /src/templates/modules/_feature.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Feature

6 | 7 |
8 | Follow 9 |
10 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: { 3 | main: './src/js/main.jsx', 4 | search: './src/js/search.jsx', 5 | }, 6 | output: { 7 | filename: '[name].js', 8 | }, 9 | module: { 10 | loaders: [{ 11 | test: /\.jsx$/, 12 | loader: "jsx-loader" 13 | }] 14 | }, 15 | externals: { 16 | "jquery": "jQuery", 17 | "bacon": "Bacon", 18 | "react": "React" 19 | } 20 | } 21 | --------------------------------------------------------------------------------