├── .gitattributes
├── bin
├── bower.cmd
├── gulp.cmd
├── node-sass.cmd
├── bower
├── gulp
└── node-sass
├── webpack.config.js
├── src
├── images
│ ├── gulp-logo.png
│ ├── sass-logo.png
│ ├── twbs-logo.png
│ ├── react-logo.png
│ └── webpack-logo.png
├── scripts
│ ├── mixins
│ │ └── SetIntervalMixin.coffee
│ ├── components
│ │ ├── Masthead.coffee
│ │ ├── HeartbeatAnimationGroup.coffee
│ │ └── StarterApp.coffee
│ └── main.litcoffee
├── styles
│ └── styles.scss
└── index.html
├── gulpfile.js
├── bower.json
├── .gitignore
├── README.md
├── LICENSE
├── package.json
├── webpack.config.litcoffee
└── gulpfile.litcoffee
/.gitattributes:
--------------------------------------------------------------------------------
1 | * -lf
2 | *.cmd -crlf
3 |
--------------------------------------------------------------------------------
/bin/bower.cmd:
--------------------------------------------------------------------------------
1 | @echo off
2 | .\node_modules\.bin\bower %*
3 |
--------------------------------------------------------------------------------
/bin/gulp.cmd:
--------------------------------------------------------------------------------
1 | @echo off
2 | .\node_modules\.bin\gulp %*
3 |
--------------------------------------------------------------------------------
/bin/node-sass.cmd:
--------------------------------------------------------------------------------
1 | @echo off
2 | .\node_modules\.bin\gulp %*
3 |
--------------------------------------------------------------------------------
/bin/bower:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ./node_modules/bower/bin/bower $*
--------------------------------------------------------------------------------
/bin/gulp:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ./node_modules/gulp/bin/gulp.js $*
4 |
--------------------------------------------------------------------------------
/bin/node-sass:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ./node_modules/sass-loader/node_modules/node-sass/bin/node-sass $*
4 |
5 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | require('coffee-script/register');
2 | module.exports = require('./webpack.config.litcoffee');
3 |
--------------------------------------------------------------------------------
/src/images/gulp-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glebm/gulp-webpack-react-bootstrap-sass-template/HEAD/src/images/gulp-logo.png
--------------------------------------------------------------------------------
/src/images/sass-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glebm/gulp-webpack-react-bootstrap-sass-template/HEAD/src/images/sass-logo.png
--------------------------------------------------------------------------------
/src/images/twbs-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glebm/gulp-webpack-react-bootstrap-sass-template/HEAD/src/images/twbs-logo.png
--------------------------------------------------------------------------------
/src/images/react-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glebm/gulp-webpack-react-bootstrap-sass-template/HEAD/src/images/react-logo.png
--------------------------------------------------------------------------------
/src/images/webpack-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glebm/gulp-webpack-react-bootstrap-sass-template/HEAD/src/images/webpack-logo.png
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | // So kill me, I like coffeescript. Im allergic to curly braces.
2 | require('coffee-script/register');
3 | require('./gulpfile.litcoffee');
4 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 | Glyphicon
42 | glyphicon-user
43 |
44 |
45 |
46 | React expression
47 | {'{15 * 20}'}
48 | {15 * 20}
49 |
50 |
51 |
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------