├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── bin ├── bower ├── bower.cmd ├── gulp ├── gulp.cmd ├── node-sass └── node-sass.cmd ├── bower.json ├── gulpfile.js ├── gulpfile.litcoffee ├── package.json ├── src ├── images │ ├── gulp-logo.png │ ├── react-logo.png │ ├── sass-logo.png │ ├── twbs-logo.png │ └── webpack-logo.png ├── index.html ├── scripts │ ├── components │ │ ├── HeartbeatAnimationGroup.coffee │ │ ├── Masthead.coffee │ │ └── StarterApp.coffee │ ├── main.litcoffee │ └── mixins │ │ └── SetIntervalMixin.coffee └── styles │ └── styles.scss ├── webpack.config.js └── webpack.config.litcoffee /.gitattributes: -------------------------------------------------------------------------------- 1 | * -lf 2 | *.cmd -crlf 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### SublimeText ### 2 | *.sublime-workspace 3 | 4 | ### OSX ### 5 | .DS_Store 6 | .AppleDouble 7 | .LSOverride 8 | Icon 9 | 10 | # Thumbnails 11 | ._* 12 | 13 | # Files that might appear on external disk 14 | .Spotlight-V100 15 | .Trashes 16 | 17 | ### Windows ### 18 | # Windows image file caches 19 | Thumbs.db 20 | ehthumbs.db 21 | 22 | # Folder config file 23 | Desktop.ini 24 | 25 | # Recycle Bin used on file shares 26 | $RECYCLE.BIN/ 27 | 28 | # App specific 29 | *.log 30 | node_modules/ 31 | bower_components/ 32 | dist/ 33 | .tmp 34 | tmp/ 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Gleb Mazovetskiy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gulp + Webpack (CoffeeScript, Sass, JSX) + React + Bootstrap for Sass 2 | 3 | This is a template for a client-side web app compiled using Gulp with Webpack. Bootstrap for Sass and React included. 4 | 5 | ## Running 6 | 7 | Clone the repo: 8 | 9 | ```console 10 | $ git clone --depth 1 git@github.com:glebm/gulp-webpack-react-bootstrap-sass-template.git 11 | ``` 12 | 13 | Install dependencies from npm and bower: 14 | 15 | ```console 16 | $ npm install 17 | $ bin/bower install 18 | ``` 19 | 20 | Start development server: 21 | 22 | ```console 23 | $ bin/gulp dev 24 | ``` 25 | 26 | See your app at [http://localhost:4000/](http://localhost:4000/). 27 | 28 | The browser will automatically reload when any file in /src changes. 29 | 30 | Production build: 31 | 32 | ```console 33 | $ bin/gulp prod 34 | ``` 35 | 36 | -------------------------------------------------------------------------------- /bin/bower: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ./node_modules/bower/bin/bower $* -------------------------------------------------------------------------------- /bin/bower.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | .\node_modules\.bin\bower %* 3 | -------------------------------------------------------------------------------- /bin/gulp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ./node_modules/gulp/bin/gulp.js $* 4 | -------------------------------------------------------------------------------- /bin/gulp.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | .\node_modules\.bin\gulp %* 3 | -------------------------------------------------------------------------------- /bin/node-sass: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ./node_modules/sass-loader/node_modules/node-sass/bin/node-sass $* 4 | 5 | -------------------------------------------------------------------------------- /bin/node-sass.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | .\node_modules\.bin\gulp %* 3 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gulp-webpack-react-bootstrap-sass-template", 3 | "version": "0.0.0", 4 | "authors": [ 5 | "Gleb Mazovetskiy " 6 | ], 7 | "license": "MIT", 8 | "private": true, 9 | "ignore": [ 10 | "**/.*", 11 | "node_modules", 12 | "bower_components", 13 | "test", 14 | "tests" 15 | ], 16 | "dependencies": { 17 | "react": "~0.13.3", 18 | "es5-shim": "~4.1.10", 19 | "bootstrap-sass": "~3.3.5", 20 | "jquery": "~2.1.4" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | // So kill me, I like coffeescript. Im allergic to curly braces. 2 | require('coffee-script/register'); 3 | require('./gulpfile.litcoffee'); 4 | -------------------------------------------------------------------------------- /gulpfile.litcoffee: -------------------------------------------------------------------------------- 1 | # Gulp Asset Pipeline Configuration 2 | 3 | This [Gulp](https://github.com/gulpjs/gulp/) configuration file defines tasks to compile the assets. 4 | It also defines a development asset web server task. 5 | 6 | ## Require packages 7 | 8 | Gulp to handle the pipeline flow: 9 | 10 | _ = require('lodash') 11 | del = require('del') 12 | deployToGithubPages = require('gulp-gh-pages') 13 | g = require('gulp') 14 | gutil = require('gulp-util') 15 | gzip = require('gulp-gzip') 16 | notify = require('gulp-notify') 17 | runSequence = require('run-sequence') 18 | vinylPaths = require('vinyl-paths') 19 | watch = require('gulp-watch') 20 | webserver = require('gulp-webserver') 21 | 22 | Webpack to compile the assets: 23 | 24 | webpack = require('webpack') 25 | 26 | A number of low-level utilities: 27 | 28 | util = require('util') 29 | tty = require('tty') 30 | path = require('path') 31 | through2 = require('through2') 32 | 33 | 34 | A helper for requiring files uncached (useful for config files) 35 | 36 | requireUncached = (path) -> 37 | delete require.cache[require.resolve(path)] 38 | require(path) 39 | 40 | ## Configuration 41 | 42 | 43 | All file system paths used in the tasks will be from the `paths` object. 44 | 45 | paths = {} 46 | 47 | Compilation source root: 48 | 49 | paths.src = 'src' 50 | paths.srcFiles = "#{paths.src}/**/*" 51 | 52 | Distribution destination root: 53 | 54 | paths.dist = 'dist' 55 | paths.distFiles = "#{paths.dist}/**/*" 56 | 57 | Paths handled with Webpack: 58 | 59 | paths.webpackPaths = [ 60 | 'src/scripts', 'src/scripts/**/*', 61 | 'src/styles', 'src/styles/**/*', 62 | 'src/images', 'src/images/**/*' 63 | ] 64 | 65 | Files not handled with Webpack that reference Webpack assets: 66 | 67 | paths.replaceAssetRefs = [ 68 | "#{paths.src}/index.html" 69 | ] 70 | 71 | Webpack configuration: 72 | 73 | paths.webpackConfig = './webpack.config.litcoffee' 74 | loadWebpackConfig = -> requireUncached(paths.webpackConfig) 75 | webpackConfig = loadWebpackConfig() 76 | 77 | ## Tasks 78 | 79 | Show help when invoked with no arguments 80 | 81 | g.task 'default', -> 82 | help = """ 83 | Usage: bin/gulp [command] 84 | 85 | Available commands: 86 | bin/gulp # display this help message 87 | bin/gulp dev # build and run dev server 88 | bin/gulp prod # production build, hash and gzip 89 | bin/gulp serve # run dev server 90 | bin/gulp clean # rm /dist 91 | bin/gulp build # development build 92 | bin/gulp deploy-gh-pages # deploy to Github Pages 93 | """ 94 | setTimeout (-> console.log help), 200 95 | 96 | ### `dev` 97 | 98 | Run a development server: 99 | 100 | g.task 'dev', ['build', 'serve'], -> 101 | logChange = (evt) -> gutil.log(gutil.colors.cyan(evt.path), 'changed') 102 | # Run webpack on config changes 103 | g.watch [paths.webpackConfig], (evt) -> 104 | logChange evt 105 | webpackConfig = loadWebpackConfig() 106 | g.start 'webpack' 107 | # Run build on app source changes 108 | g.watch [paths.srcFiles], (evt) -> 109 | logChange evt 110 | g.start 'build' 111 | 112 | ### `prod` 113 | 114 | Production build: 115 | 116 | g.task 'prod', (cb) -> 117 | # Apply production config, pass true to append hashes to file names 118 | setWebpackConfig loadWebpackConfig().mergeProductionConfig() 119 | runSequence 'clean', 'build', 'gzip', cb 120 | 121 | ### `clean` 122 | 123 | Clean (remove) the distribution folder: 124 | 125 | g.task 'clean', -> 126 | g.src(paths.dist, read: false).pipe(vinylPaths(del)) 127 | 128 | ### `build` 129 | 130 | Build all assets (development build): 131 | 132 | g.task 'build', (cb) -> 133 | runSequence 'webpack', 'build-replace-asset-refs', 'copy', cb 134 | 135 | ### `serve` 136 | 137 | Serve dist folder and inject livereload 138 | 139 | g.task 'serve', ['build'], -> 140 | g.src('./dist').pipe webserver 141 | livereload: 142 | enable: true 143 | port: 35729 144 | host: 'localhost' 145 | port: 4000 146 | 147 | ### `webpack` 148 | 149 | Run webpack to process CoffeeScript, JSX, Sass, inline small resources into the CSS, etc: 150 | 151 | g.task 'webpack', (cb) -> 152 | gutil.log("[webpack]", 'Compiling...') 153 | webpack webpackConfig, (err, stats) -> 154 | if (err) then throw new gutil.PluginError("webpack", err) 155 | gutil.log("[webpack]", stats.toString(colors: tty.isatty(process.stdout.fd))) 156 | cb() 157 | 158 | ### `copy` 159 | 160 | Copy non-webpack assets to the distribution: 161 | 162 | g.task 'copy', -> 163 | g.src([paths.srcFiles].concat(paths.webpackPaths.concat(paths.replaceAssetRefs).map (path) -> "!#{path}")).pipe(g.dest paths.dist) 164 | 165 | ### `gzip` 166 | 167 | GZip assets: 168 | 169 | g.task 'gzip', -> 170 | g.src(paths.distFiles) 171 | .on('error', handleErrors) 172 | .pipe(gzip()) 173 | .pipe(g.dest paths.dist) 174 | 175 | ### `build-replace-asset-refs` 176 | 177 | Add fingerprinting hashes to asset references: 178 | 179 | g.task 'build-replace-asset-refs', -> 180 | g.src(paths.replaceAssetRefs).pipe( 181 | replaceWebpackAssetUrlsInFiles( 182 | requireUncached("./#{paths.dist}/assets/asset-stats.json"), 183 | webpackConfig.output.publicPath 184 | )).pipe(g.dest paths.dist) 185 | 186 | ### `deploy-gh-pages` 187 | 188 | Build for gh-pages-branch. Same as production but do not append hashes: 189 | 190 | g.task 'build-gh-pages', (cb) -> 191 | webpackConfig = _.merge loadWebpackConfig().mergeProductionConfig(), 192 | output: 193 | publicPath: "/gulp-webpack-react-bootstrap-sass-template/assets/" 194 | runSequence 'clean', 'build', cb 195 | 196 | Deploy to gh-pages branch: 197 | 198 | g.task 'deploy-gh-pages', ['build-gh-pages'], -> 199 | g.src(paths.distFiles).pipe(deployToGithubPages(cacheDir: './tmp/.gh-pages-cache')) 200 | 201 | ## Helpers 202 | 203 | Set the "global" `webpackConfig` to argument. 204 | 205 | setWebpackConfig = (conf) -> 206 | webpackConfig = conf 207 | 208 | ### `replaceWebpackAssetUrlsInFile` 209 | 210 | Replace asset URLs with the ones from Webpack in a file: 211 | 212 | replaceWebpackAssetUrlsInFiles = (stats, publicPath) -> 213 | 214 | Return a `through2` object (gulp plugin) that replaces file contents in Vinyl virtual file system: 215 | 216 | through2.obj (vinylFile, enc, cb) -> 217 | vinylFile.contents = new Buffer(replaceWebpackAssetUrls(String(vinylFile.contents), stats, publicPath)) 218 | @push vinylFile 219 | cb() 220 | 221 | ### `replaceWebpackAssetUrls` 222 | 223 | Replace asset URLs with the ones from Webpack: 224 | 225 | replaceWebpackAssetUrls = (text, stats, publicPath) -> 226 | 227 | For each entry in Webpack stats, such as `{'main': 'assets/main-abcde.js'}`: 228 | 229 | for entryName, targetPath of stats 230 | 231 | If source-maps are on, then targetPath is an array such as `['file.js', 'file.js.map'`]`. Get the right file: 232 | 233 | if util.isArray(targetPath) 234 | targetPath = _.find targetPath, (p) -> path.extname(p).toLowerCase() != '.map' 235 | 236 | Replace logical path with the target path: 237 | 238 | ref = "assets/#{entryName}#{path.extname(targetPath)}" 239 | text = text.replace ref, publicPath + targetPath 240 | 241 | All done: 242 | 243 | text 244 | 245 | ### `handleErrors` 246 | 247 | Route non-gulp errors through gulp-notify: 248 | 249 | handleErrors = (args...) -> 250 | notify.onError(title: 'Error', message: '<%= error.message %>').apply(@, args) 251 | @emit 'end' 252 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Gleb Mazovetskiy", 3 | "name": "gulp-webpack-react-bootstrap-sass-template", 4 | "version": "0.0.0", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/glebm/gulp-webpack-react-bootstrap-sass-template.git" 8 | }, 9 | "devDependencies": { 10 | "autoprefixer-loader": "^2.0.0", 11 | "bower": "1.4.1", 12 | "coffee-loader": "~0.7.2", 13 | "coffee-script": "*", 14 | "css-loader": "^0.15.5", 15 | "del": "^1.2.0", 16 | "extract-text-webpack-plugin": "~0.8.2", 17 | "file-loader": "^0.8.4", 18 | "gulp": "^3.9.0", 19 | "gulp-coffee": "~2.3.1", 20 | "gulp-gh-pages": "^0.5.2", 21 | "gulp-gzip": "1.2.0", 22 | "gulp-livereload": "~3.8.0", 23 | "gulp-minify-css": "~1.2.0", 24 | "gulp-notify": "^2.2.0", 25 | "gulp-rev": "^5.1.0", 26 | "gulp-uglify": "~1.2.0", 27 | "gulp-util": "*", 28 | "gulp-watch": "~4.3.4", 29 | "gulp-webserver": "latest", 30 | "jsx-loader": "0.13.2", 31 | "lodash": "^3.10.0", 32 | "node-sass": "^3.2.0", 33 | "run-sequence": "^1.1.2", 34 | "sass-loader": "^1.0.3", 35 | "script-loader": "~0.6.1", 36 | "style-loader": "^0.12.3", 37 | "through2": "^2.0.0", 38 | "tmp": "0.0.26", 39 | "url-loader": "^0.5.6", 40 | "vinyl-paths": "^1.0.0", 41 | "webpack": "~1.10.5", 42 | "webpack-dev-server": "~1.10.1" 43 | }, 44 | "license": "MIT" 45 | } 46 | -------------------------------------------------------------------------------- /src/images/gulp-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glebm/gulp-webpack-react-bootstrap-sass-template/b17e077569bb6e0c566fa2f5efc173a9ed619fc2/src/images/gulp-logo.png -------------------------------------------------------------------------------- /src/images/react-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glebm/gulp-webpack-react-bootstrap-sass-template/b17e077569bb6e0c566fa2f5efc173a9ed619fc2/src/images/react-logo.png -------------------------------------------------------------------------------- /src/images/sass-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glebm/gulp-webpack-react-bootstrap-sass-template/b17e077569bb6e0c566fa2f5efc173a9ed619fc2/src/images/sass-logo.png -------------------------------------------------------------------------------- /src/images/twbs-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glebm/gulp-webpack-react-bootstrap-sass-template/b17e077569bb6e0c566fa2f5efc173a9ed619fc2/src/images/twbs-logo.png -------------------------------------------------------------------------------- /src/images/webpack-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glebm/gulp-webpack-react-bootstrap-sass-template/b17e077569bb6e0c566fa2f5efc173a9ed619fc2/src/images/webpack-logo.png -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | App 12 | 13 | 14 | 15 | 16 | 17 | 21 | 22 | 23 | 26 | 27 | 28 | 29 | 30 | See on GitHub 31 | 32 |
33 |

34 | Loading ... 35 |

36 |
37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/scripts/components/HeartbeatAnimationGroup.coffee: -------------------------------------------------------------------------------- 1 | `/** @jsx React.DOM */` 2 | 3 | HeartbeatAnimationGroup = React.createClass 4 | mixins: [require('scripts/mixins/SetIntervalMixin.coffee')] 5 | 6 | getDefaultProps: -> 7 | phase: 0 8 | center: 0.7 9 | amplitude: 0.2 10 | steps: 2 11 | transition: 'all 650ms ease-in-out' 12 | tickInterval: 650 13 | 14 | getInitialState: -> 15 | ticks: 0 16 | 17 | componentDidMount: -> 18 | setTimeout (=> @setInterval (=> @tick()), 650), @props.phase 19 | 20 | tick: -> 21 | @setState(ticks: @state.ticks + 1) 22 | 23 | scale: -> 24 | @props.center + @props.amplitude / 2.0 - @props.amplitude / (1 + @state.ticks % @props.steps) 25 | 26 | render: -> 27 | `( 28 |
29 | {this.props.children} 30 |
31 | )` 32 | 33 | module.exports = HeartbeatAnimationGroup 34 | -------------------------------------------------------------------------------- /src/scripts/components/Masthead.coffee: -------------------------------------------------------------------------------- 1 | `/** @jsx React.DOM */` 2 | 3 | Masthead = React.createClass 4 | 5 | render: -> 6 | `( 7 |
8 |
9 |

{this.props.title}

10 |
{this.props.children}
11 |
12 |
13 | )` 14 | 15 | module.exports = Masthead 16 | -------------------------------------------------------------------------------- /src/scripts/components/StarterApp.coffee: -------------------------------------------------------------------------------- 1 | `/** @jsx React.DOM */` 2 | 3 | cx = React.addons.classSet 4 | Masthead = require("./Masthead.coffee") 5 | HeartbeatAnimationGroup = require("./HeartbeatAnimationGroup.coffee") 6 | 7 | Tag = React.createClass 8 | render: -> 9 | `( 10 | {this.props.children} 11 | )` 12 | 13 | poweredBy = [ 14 | { logoURL: require('images/gulp-logo.png') } 15 | { logoURL: require('images/webpack-logo.png') } 16 | { logoURL: require('images/react-logo.png') } 17 | { logoURL: require('images/sass-logo.png') } 18 | { logoURL: require('images/twbs-logo.png') } 19 | ] 20 | 21 | StarterApp = React.createClass 22 | render: -> 23 | `( 24 |
25 | 26 |

27 | This template brings together all the pieces you need to start building your first React app.
28 | Gulp is used for orchestrating the build process, and Webpack is used to compile and package assets. 29 |

30 |

31 | 32 | This template on Github 33 | 34 |

35 |

36 | Sass Literate CoffeeScript JSX Autoprefixer 37 |

38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 |
Glyphicon glyphicon-user
React expression {'{15 * 20}'} {15 * 20}
52 |
53 |
54 |
55 |

Powered By

56 |
57 |
58 | {renderPoweredByItems(poweredBy)} 59 |
60 |
61 |
62 | )` 63 | 64 | renderPoweredByItems = (items) -> 65 | n = items.length 66 | items.map (item, i) -> 67 | imageURL = item.logoURL 68 | `( 69 | 70 | 71 | 72 | )` 73 | 74 | 75 | module.exports = StarterApp 76 | -------------------------------------------------------------------------------- /src/scripts/main.litcoffee: -------------------------------------------------------------------------------- 1 | Mark this file for JSX: 2 | 3 | `/** @jsx React.DOM */` 4 | 5 | If compiling for production, require React using native React minified package, since it also excludes debug calls: 6 | 7 | if __PRODUCTION__ 8 | require("script!react/react-with-addons.min.js") 9 | 10 | Otherwise, require React development package: 11 | 12 | else 13 | require("script!react/react-with-addons.js") 14 | 15 | **NB**: `__PRODUCTION__` is a variable defined using webpack `DefinePlugin`, it is substituted with its boolean value 16 | before evaluation. The dead `if ... else` branch is eliminated during minification. 17 | 18 | Require jQuery: 19 | 20 | require("script!jquery/dist/jquery.js") 21 | 22 | Require app: 23 | 24 | StarterApp = require('./components/StarterApp.coffee') 25 | 26 | Render app: 27 | 28 | githubUrl = 'https://github.com/glebm/gulp-webpack-react-bootstrap-sass-template' 29 | React.render(``, document.getElementById('app')) 30 | 31 | -------------------------------------------------------------------------------- /src/scripts/mixins/SetIntervalMixin.coffee: -------------------------------------------------------------------------------- 1 | `/** @jsx React.DOM */` 2 | 3 | SetIntervalMixin = 4 | componentWillMount: -> 5 | @intervals = [] 6 | setInterval: (args...) -> 7 | @intervals.push(setInterval.apply(null, args)) 8 | componentWillUnmount: -> 9 | @intervals.map(clearInterval) 10 | 11 | module.exports = SetIntervalMixin 12 | -------------------------------------------------------------------------------- /src/styles/styles.scss: -------------------------------------------------------------------------------- 1 | $icon-font-path: '~bootstrap-sass/assets/fonts/bootstrap/'; 2 | $brand-primary: #8355ff; 3 | @import "bootstrap-sass/assets/stylesheets/bootstrap"; 4 | 5 | body { 6 | background-color: lighten($brand-primary, 30%); 7 | } 8 | 9 | .bs-masthead { 10 | position: relative; 11 | padding: 30px 15px; 12 | text-align: center; 13 | text-shadow: 0 1px 0 rgba(0,0,0,.15); 14 | h1 { 15 | line-height: 1; 16 | color: darken($brand-primary, 35%); 17 | @media (min-width: $screen-sm-min) { 18 | font-size: 50px; 19 | } 20 | } 21 | } 22 | 23 | .fade-enter { 24 | opacity: 0.01; 25 | transition: opacity 5s ease-in; 26 | } 27 | 28 | .fade-enter.fade-enter-active { 29 | opacity: 1; 30 | } 31 | 32 | .test-features { 33 | max-width: 500px; 34 | margin: 0 auto; 35 | } 36 | 37 | .powered-by-panel { 38 | max-width: 1032px; 39 | margin: 0 auto; 40 | text-align: center; 41 | > .panel-body > div { 42 | display: inline-block; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | require('coffee-script/register'); 2 | module.exports = require('./webpack.config.litcoffee'); 3 | -------------------------------------------------------------------------------- /webpack.config.litcoffee: -------------------------------------------------------------------------------- 1 | # Webpack Configuration 2 | 3 | [Webpack](https://github.com/webpack/webpack) handles asset compilation (Sass, CoffeeScript, etc). 4 | It also manages loading via JavaScript `require`, Sass `@import`, and CSS `url()`. 5 | 6 | First, require the dependencies: 7 | 8 | webpack = require('webpack') 9 | fs = require('fs') 10 | path = require('path') 11 | _ = require('lodash') 12 | 13 | 14 | Define an empty configuration: 15 | 16 | module.exports = {} 17 | 18 | Entry bundles, i.e. package source paths. See [docs](http://webpack.github.io/docs/configuration.html#entry). 19 | 20 | entries = module.exports.entry = 21 | "main" : "./src/scripts/main.litcoffee" 22 | "styles" : "./src/styles/styles.scss" 23 | "vendor/es5-shim" : "./bower_components/es5-shim/es5-shim.js" 24 | "vendor/es5-sham" : "./bower_components/es5-shim/es5-sham.js" 25 | 26 | Define compiled distribution output directory: 27 | 28 | outputDir = path.join(__dirname, "dist", "assets") 29 | 30 | ## Loaders 31 | 32 | Define how files should be loaded (required) based on the extension. 33 | 34 | ### Scripts 35 | 36 | Process CoffeeScript and JSX. 37 | 38 | jsLoaders = ["jsx"] 39 | scriptModLoaders = [ 40 | { test: /\.coffee$/ , loaders: jsLoaders.concat(["coffee"]) } 41 | { test: /\.litcoffee$/, loaders: jsLoaders.concat(["coffee?literate"]) } 42 | { test: /\.js$/ , loaders: jsLoaders } 43 | ] 44 | 45 | ### Styles 46 | 47 | Process Sass and use Autoprefixer. 48 | 49 | cssLoaders = ['style', 'css', 'autoprefixer-loader?browsers=last 2 versions'] 50 | styleModLoaders = [ 51 | { test: /\.scss$/, loaders: cssLoaders.concat([ 52 | "sass?precision=10&outputStyle=expanded&sourceMap=true&includePaths[]=" + path.resolve(__dirname, './bower_components')]) } 53 | { test: /\.css$/ , loaders: cssLoaders } 54 | ] 55 | 56 | ### Static assets 57 | 58 | Embed data-URLs into CSS and JS for small images and `.woff` fonts: 59 | 60 | staticModLoaders = [ 61 | { test: /\.gif$/ , loader: "url?limit=10000&mimetype=image/gif" } 62 | { test: /\.jpg$/ , loader: "url?limit=10000&mimetype=image/jpg" } 63 | { test: /\.png$/ , loader: "url?limit=10000&mimetype=image/png" } 64 | { test: /\.woff$/ , loader: "url?limit=10000&mimetype=application/font-woff" } 65 | { test: /\.woff2$/, loader: "url?limit=10000&mimetype=application/font-woff2" } 66 | { test: /\.ttf$/ , loader: "file?mimetype=application/vnd.ms-fontobject" } 67 | { test: /\.eot$/ , loader: "file?mimetype=application/x-font-ttf" } 68 | { test: /\.svg$/ , loader: "file?mimetype=image/svg+xml" } 69 | ] 70 | 71 | ### Output CSS to `.css` files 72 | 73 | `ExtractTextPlugin` is an experimental plugin to output CSS in `.css` and not as `.js` files with embedded CSS strings. 74 | 75 | Require the plugin: 76 | 77 | ExtractTextPlugin = require("extract-text-webpack-plugin") 78 | 79 | Set `styleModLoaders` to use the plugin: 80 | 81 | styleModLoaders = styleModLoaders.map (e) -> 82 | { test: e.test, loader: ExtractTextPlugin.extract(e.loaders.slice(1).join('!')) } 83 | 84 | Create an instance of the extractTextPlugin: 85 | 86 | extractTextPlugin = new ExtractTextPlugin("[name].css", allChunks: true) 87 | 88 | ### Define macro variables 89 | 90 | `DefinePlugin` defines variables to be substituted in the assets (a la macro). 91 | 92 | Define `__PRODUCTION__: false` variable (set to true on `useProductionSettings()`): 93 | 94 | definePlugin = new webpack.DefinePlugin( 95 | __PRODUCTION__: JSON.stringify(false) 96 | ) 97 | 98 | ### Generate a manifest 99 | 100 | Generate a manifest mapping logical paths to actual paths: 101 | 102 | generateManifestPlugin = (compiler) -> 103 | @plugin 'done', (stats) -> 104 | stats = stats.toJson() 105 | 106 | Set target path extension to `.css` for style assets, because we use `ExtractTextPlugin`: 107 | 108 | assetStats = stats.assetsByChunkName 109 | setCssExt = (p) -> p.replace(/\.js$/, '.css') 110 | for entryName, entryPath of assetStats when /\.(?:scss|sass|css)$/.test(entries[entryName]) 111 | if _.isArray(entryPath) 112 | assetStats[entryName] = entryPath.map (p) -> setCssExt(p) 113 | else 114 | assetStats[entryName] = setCssExt(entryPath) 115 | 116 | Write asset-ref -> asset-hash manifest to outputDir/stats.json 117 | 118 | fs.writeFileSync(path.join(outputDir, "asset-stats.json"), JSON.stringify(stats.assetsByChunkName, null, 2)) 119 | 120 | ### Other options 121 | 122 | _.merge module.exports, 123 | 124 | Set compilation target to "web". See [docs](http://webpack.github.io/docs/configuration.html#target). 125 | 126 | target: "web" 127 | 128 | Set development options by default: 129 | 130 | cache: true 131 | debug: true 132 | # We are watching in Gulp, so tell webpack not to watch 133 | watch: false 134 | # watchDelay: 300 135 | devtool: 'source-map' 136 | 137 | Output options: 138 | 139 | output: 140 | path : outputDir 141 | publicPath : "/assets/" 142 | filename : "[name].js" 143 | chunkFilename: "[name].[id].[chunkhash].js" 144 | 145 | Look for required files in bower and node 146 | 147 | resolve: 148 | modulesDirectories: [ 149 | 'src' 150 | 'bower_components' 151 | 'node_modules' 152 | ] 153 | 154 | Define how modules should be loaded based on path extension: 155 | 156 | module: 157 | loaders: styleModLoaders.concat(scriptModLoaders).concat(staticModLoaders) 158 | 159 | Define the plugins: 160 | 161 | plugins: [ 162 | definePlugin 163 | extractTextPlugin 164 | generateManifestPlugin 165 | ] 166 | 167 | 168 | ## Production overrides 169 | 170 | Export a method that applies production settings (used in gulpfile): 171 | 172 | mergeProductionConfig: (addHashes = true) -> 173 | 174 | Production plugins: 175 | 176 | Set `__PRODUCTION__` to true in the `DefinePlugin` instance: 177 | 178 | definePlugin.definitions.__PRODUCTION__ = JSON.stringify(true) 179 | 180 | Add content hashes to the output filenames: 181 | 182 | _.merge @, 183 | output: 184 | filename: "[name]-[hash].js" 185 | 186 | Tell `ExtractTextPlugin` to append hashes: 187 | 188 | extractTextPlugin.filename = '[name]-[hash].css' 189 | 190 | Disable development settings: 191 | 192 | _.merge @, 193 | debug: false 194 | watch: false 195 | devtool: null 196 | 197 | Turn on production optimizations: 198 | 199 | plugins: @plugins.concat [ 200 | 201 | Order the modules and chunks by occurrence. This saves space, because often referenced modules and chunks get smaller ids. 202 | 203 | new webpack.optimize.OccurenceOrderPlugin(true) 204 | 205 | Minify JavaScript with UglifyJS: 206 | 207 | new webpack.optimize.UglifyJsPlugin() 208 | 209 | ] 210 | 211 | [Learn more](https://github.com/webpack/docs/wiki/internal-webpack-plugins#optimize) 212 | about optimization plugins shipped with Webpack. 213 | --------------------------------------------------------------------------------