├── .babelrc ├── .dockerignore ├── .editorconfig ├── .gitignore ├── Dockerfile ├── README.md ├── gulpfile.js ├── package.json └── src ├── app └── app.jsx ├── assets └── scss │ └── app.scss ├── server ├── index.js └── routes.js └── templates └── index.html /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react", "stage-0"] 3 | } 4 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | public 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [**.{js,jsx,json}] 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [**.scss] 17 | indent_style = space 18 | indent_size = 2 19 | 20 | [**.{html,handlebars,svg}] 21 | indent_style = space 22 | indent_size = 2 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .sass-cache 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:5 2 | 3 | MAINTAINER You 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Docker Tutorial 2 | 3 | This is a React front-end and Node back-end sample application, meant to be set up using Docker. 4 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | var gutil = require('gulp-util'); 5 | var del = require('del'); 6 | var uglify = require('gulp-uglify'); 7 | var gulpif = require('gulp-if'); 8 | var exec = require('child_process').exec; 9 | var buffer = require('vinyl-buffer'); 10 | var argv = require('yargs').argv; 11 | var sourcemaps = require('gulp-sourcemaps'); 12 | 13 | // sass 14 | var sass = require('gulp-sass'); 15 | var postcss = require('gulp-postcss'); 16 | var autoprefixer = require('autoprefixer'); 17 | 18 | // js 19 | var browserify = require('browserify'); 20 | var source = require('vinyl-source-stream'); 21 | var babelify = require('babelify'); 22 | var nodemon = require('gulp-nodemon'); 23 | 24 | // production flag 25 | var production = !argv.dev; 26 | 27 | var syncbrowser = argv.browsersync; 28 | 29 | // determine if we're doing a build 30 | // and if so, bypass the livereload 31 | var build = argv._.length ? argv._[0] === 'build' : false; 32 | var watch = argv._.length ? argv._[0] === 'watch' : true; 33 | 34 | gutil.log(gutil.colors.bgGreen('[Gulp flags]', 'production:', production, '| build:', build, '| watch:', watch, "| syncbrowser:", syncbrowser)); 35 | 36 | if (watch) { 37 | var watchify = require('watchify'); 38 | } 39 | 40 | if (syncbrowser) { 41 | var browserSync = require('browser-sync').create(); 42 | } 43 | 44 | var reloadbrowsersync = function() { 45 | if (syncbrowser) { 46 | browserSync.reload(); 47 | } 48 | } 49 | 50 | // ---------------------------- 51 | // Error notification methods 52 | // ---------------------------- 53 | var handleError = function(task) { 54 | return function(err) { 55 | gutil.log(gutil.colors.bgRed(task + ' error:'), gutil.colors.red(err)); 56 | if (watch) this.emit('end'); 57 | }; 58 | }; 59 | 60 | // -------------------------- 61 | // CUSTOM TASK METHODS 62 | // -------------------------- 63 | var tasks = { 64 | // -------------------------- 65 | // Delete build folder 66 | // -------------------------- 67 | clean: function() { 68 | del.sync(['public']); 69 | 70 | return gulp.src('.gitignore') 71 | .pipe(gulp.dest('public/') 72 | ); 73 | }, 74 | // -------------------------- 75 | // CSS compilation (LibSass + Autoprefixer) 76 | // -------------------------- 77 | sass: function() { 78 | return gulp.src('src/assets/scss/[^_]*.scss') 79 | // sourcemaps + sass + error handling 80 | .pipe(gulpif(!production, sourcemaps.init())) 81 | .pipe(sass({ 82 | errLogToConsole: true, 83 | sourceComments: !production, 84 | outputStyle: production ? 'compressed' : 'nested' 85 | })) 86 | .on('error', function(err) { 87 | sass.logError.bind(this, err)(); 88 | }) 89 | // generate .maps 90 | .pipe(gulpif(!production, sourcemaps.write({ 91 | 'includeContent': false, 92 | 'sourceRoot': '.' 93 | }))) 94 | // autoprefixer 95 | .pipe(gulpif(!production, sourcemaps.init({ 96 | 'loadMaps': true 97 | }))) 98 | .pipe(postcss([autoprefixer({browsers: ['last 2 versions']})])) 99 | // we don't serve the source files 100 | // so include scss content inside the sourcemaps 101 | .pipe(sourcemaps.write({ 102 | 'includeContent': true 103 | })) 104 | // write sourcemaps to a specific directory 105 | // give it a file and save 106 | .pipe(gulp.dest('public/css')); 107 | }, 108 | // -------------------------- 109 | // Browserify bundles (Babelify + Watchify) 110 | // -------------------------- 111 | babelify: function() { 112 | // Create a separate vendor bundler that will only run when starting gulp 113 | var vendorBundler = browserify({ 114 | debug: !production // Sourcemapping 115 | }) 116 | .require('react'); 117 | 118 | var bundler = browserify({ 119 | debug: !production, // Sourcemapping 120 | cache: {}, 121 | packageCache: {}, 122 | fullPaths: watch 123 | }) 124 | .require(require.resolve('./src/app/app.jsx'), { entry: true }) 125 | .transform('babelify') 126 | .external('react'); 127 | 128 | if (watch) { 129 | bundler = watchify(bundler, {poll: true}); 130 | } 131 | 132 | var rebundle = function() { 133 | var result = bundler.bundle() 134 | .on('error', handleError('Browserify')) 135 | .pipe(source('app.js')) 136 | .pipe(buffer()) 137 | .pipe(gulpif(production, uglify())) 138 | .pipe(gulpif(!production, sourcemaps.init({loadMaps: true}))) 139 | .pipe(gulpif(!production, sourcemaps.write('./'))) 140 | .pipe(gulp.dest('public/js/')); 141 | 142 | if(syncbrowser) { 143 | return result.pipe(browserSync.reload({stream:true, once: true})); 144 | } 145 | 146 | return result; 147 | }; 148 | 149 | if (watch) { 150 | bundler.on('update', rebundle); 151 | bundler.on('log', function (msg) { 152 | gutil.log('Babelify rebundle:', msg); 153 | }); 154 | } 155 | 156 | vendorBundler.bundle() 157 | .pipe(source('vendors.js')) 158 | .pipe(buffer()) 159 | .pipe(gulpif(production, uglify())) 160 | .pipe(gulp.dest('public/js/')); 161 | 162 | return rebundle(); 163 | }, 164 | // -------------------------- 165 | // Node server (Nodemon + BrowserSync) 166 | // -------------------------- 167 | serve: function(cb) { 168 | var started = false; 169 | 170 | return nodemon({ 171 | verbose: true, 172 | legacyWatch: true, 173 | script: 'src/server/index.js', 174 | watch: ['src/server', 'src/templates'], 175 | execMap: { 176 | 'js': 'babel-node' 177 | }, 178 | ext: 'js html', 179 | stdout: false, 180 | env: { 181 | 'NODE_ENV': 'development', 182 | 'PORT': syncbrowser ? 8878 : 8877 183 | } 184 | }).on('start', function () { 185 | gutil.log(gutil.colors.bgGreen('Nodemon ' + (started ? 're' : 'first ') + 'start...')); 186 | if (!started) { 187 | started = true; 188 | if (syncbrowser) { 189 | browserSync.init(null, { 190 | port: 8877, 191 | proxy: { 192 | target: 'localhost:8878' 193 | }, 194 | open: false 195 | }); 196 | } 197 | cb(); 198 | } 199 | }).on('readable', function(data) { 200 | // this is the best hack I have found so far to reload browsers once the 201 | // server is actually running after a restart, not just starting up 202 | this.stdout.on('data', function(chunk) { 203 | if (/up and running on/.test(chunk)) { 204 | reloadbrowsersync(); 205 | } 206 | process.stdout.write(chunk); 207 | }); 208 | this.stderr.pipe(process.stderr); 209 | }); 210 | } 211 | }; 212 | 213 | gulp.task('reload-sass', ['sass'], function(){ 214 | reloadbrowsersync(); 215 | }); 216 | 217 | // -------------------------- 218 | // CUSTOMS TASKS 219 | // -------------------------- 220 | gulp.task('clean', tasks.clean); 221 | gulp.task('sass', tasks.sass); 222 | gulp.task('babelify', tasks.babelify); 223 | gulp.task('serve', ['build'], tasks.serve); 224 | gulp.task('start', ['clean', 'serve']); 225 | 226 | // build task 227 | gulp.task('build', [ 228 | 'sass', 229 | 'babelify' 230 | ]); 231 | 232 | 233 | // -------------------------- 234 | // DEV/WATCH TASK 235 | // -------------------------- 236 | gulp.task('watch', ['start'], function() { 237 | gulp.watch(['src/assets/scss/**/*.scss'], ['reload-sass']); 238 | gutil.log(gutil.colors.bgGreen('Watching for changes...')); 239 | }); 240 | 241 | gulp.task('default', ['start']); 242 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docker-tutorial", 3 | "version": "1.0.0", 4 | "description": "Docker tutorial with a Node app.", 5 | "repository": { 6 | "type": "git", 7 | "url": "git@github.com:ustwo/docker-tutorial.git" 8 | }, 9 | "license": "MIT", 10 | "private": true, 11 | "scripts": { 12 | "compile": "gulp build", 13 | "dev": "gulp start --dev", 14 | "watch": "gulp watch --dev", 15 | "browsersync": "gulp watch --browsersync --dev" 16 | }, 17 | "dependencies": { 18 | "autoprefixer": "6.1.0", 19 | "babelify": "7.2.0", 20 | "babel-cli": "6.1.18", 21 | "babel-polyfill": "6.1.18", 22 | "babel-preset-es2015": "6.1.18", 23 | "babel-preset-react": "6.1.18", 24 | "babel-preset-stage-0": "6.1.18", 25 | "browser-sync": "2.10.0", 26 | "browserify": "12.0.1", 27 | "consolidate": "0.13.1", 28 | "del": "2.1.0", 29 | "express": "4.13.3", 30 | "gulp": "3.9.0", 31 | "gulp-if": "2.0.0", 32 | "gulp-nodemon": "2.0.4", 33 | "gulp-postcss": "6.0.1", 34 | "gulp-sass": "2.1.0", 35 | "gulp-sourcemaps": "1.6.0", 36 | "gulp-uglify": "1.5.1", 37 | "gulp-util": "3.0.7", 38 | "handlebars": "4.0.4", 39 | "nodemon": "1.8.1", 40 | "normalize-libsass": "1.0.3", 41 | "react": "0.14.2", 42 | "react-dom": "0.14.2", 43 | "route-pattern": "0.0.6", 44 | "vinyl-buffer": "1.0.0", 45 | "vinyl-source-stream": "1.1.0", 46 | "watchify": "3.6.0", 47 | "yargs": "3.30.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/app/app.jsx: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import 'babel-polyfill'; 4 | import React from 'react'; 5 | import ReactDOM from 'react-dom'; 6 | 7 | const App = React.createClass({ 8 | getDefaultProps() { 9 | return { 10 | title: 'Hello Docker!' 11 | }; 12 | }, 13 | render() { 14 | return ( 15 |
16 |

{this.props.title}

17 |
18 | ); 19 | } 20 | }); 21 | 22 | ReactDOM.render( 23 | , 24 | document.getElementById('react-content') 25 | ); 26 | -------------------------------------------------------------------------------- /src/assets/scss/app.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | @import '../../../node_modules/normalize-libsass/normalize'; 4 | 5 | body { 6 | text-align: center; 7 | font-size: 3em; 8 | } 9 | 10 | .docker { 11 | color: #CCC; 12 | } 13 | -------------------------------------------------------------------------------- /src/server/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import cons from 'consolidate'; 4 | import express from 'express'; 5 | import http from 'http'; 6 | import path from 'path'; 7 | 8 | import manifest from '../../package.json'; 9 | import routes from './routes.js'; 10 | 11 | let app = express(); 12 | let publicPath = path.join(__dirname, '../../public'); 13 | 14 | app.set('port', process.env.PORT || 8877); 15 | app.set('host', process.env.VIRTUAL_HOST || `http://localhost:${app.get('port')}/`); 16 | app.engine('html', cons.handlebars); 17 | app.set('view engine', 'html'); 18 | app.set('views', path.join(__dirname, '../templates')); 19 | 20 | app.use(express.static(publicPath)); 21 | app.use('/', routes); 22 | 23 | http.createServer(app).listen(app.get('port')); 24 | console.log(`${manifest.name} ${manifest.version} up and running on ${app.get('port')}`); 25 | -------------------------------------------------------------------------------- /src/server/routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import path from 'path'; 3 | import React from 'react'; 4 | 5 | let router = express.Router(); 6 | 7 | router.get('/*', (req, res) => { 8 | res.render('index', { 9 | title: 'App' 10 | }); 11 | }); 12 | 13 | export default router; 14 | -------------------------------------------------------------------------------- /src/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{title}} 6 | 7 | 8 | 9 | 10 |

You should see a message below this paragraph if everything worked correctly.

11 |
12 | 13 | 14 | 15 | 16 | --------------------------------------------------------------------------------