-
Notifications
You must be signed in to change notification settings - Fork 20
/
gulpfile.js
434 lines (393 loc) · 16.2 KB
/
gulpfile.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
// Gulp
//
// This is not a normal require, because our gulp-help tool (which provides the
// nice task descriptions on the command-line) requires changing the function
// signature of gulp tasks to include the task description.
var gulp = require('gulp-help')(require('gulp'));
// Gulp / Node utilities
var u = require('gulp-util');
var log = u.log;
var c = u.colors;
var del = require('del');
var spawn = require('child_process').spawn;
var sequence = require('run-sequence');
// Basic workflow plugins
var prefix = require('gulp-autoprefixer');
var sass = require('gulp-sass');
var bs = require('browser-sync');
var reload = bs.reload;
// Performance workflow plugins
var concat = require('gulp-concat');
var mincss = require('gulp-minify-css');
var imagemin = require('gulp-imagemin');
var uncss = require('gulp-uncss');
var uglify = require('gulp-uglify');
var critical = require('critical').stream;
// Performance testing plugins
var psi = require('psi');
var wpt = require('webpagetest');
var ngrok = require('ngrok');
// -----------------------------------------------------------------------------
// Remove old CSS
//
// This task only deletes the files generated by the 'sass' and 'css' tasks.
// The 'uncss' task is slow to run and less frequently needed, so we keep the
// build process fast by preserving the results of uncss.
// -----------------------------------------------------------------------------
gulp.task('clean-css', false, function() {
return del(['css/{all,main}*'], function (err) {
if (err) { log(c.red('clean-css'), err); }
else {
log(
c.green('clean-css'),
'deleted old stylesheets'
);
}
});
});
// -----------------------------------------------------------------------------
// Sass Task
//
// Compiles Sass and runs the CSS through autoprefixer. A separate task will
// combine the compiled CSS with vendor files and minify the aggregate.
// -----------------------------------------------------------------------------
gulp.task('sass', 'Compiles Sass and uses autoprefixer', function() {
return gulp.src('_sass/**/*.scss')
.pipe(sass({
outputStyle: 'nested',
onSuccess: function(css) {
var dest = css.stats.entry.split('/');
log(c.green('sass'), 'compiled to', dest[dest.length - 1]);
},
onError: function(err, res) {
log(c.red('Sass failed to compile'));
log(c.red('> ') + err.file.split('/')[err.file.split('/').length - 1] + ' ' + c.underline('line ' + err.line) + ': ' + err.message);
}
}))
.pipe(prefix("last 2 versions", "> 1%"))
.pipe(gulp.dest('css'));
});
// -----------------------------------------------------------------------------
// Combine and Minify CSS
//
// This task minifies all the CSS found in the css/ directory, including the
// uncss-ed copies of bootstrap. The end result is a minified aggregate, ready
// to be served.
// -----------------------------------------------------------------------------
gulp.task('css', 'Removes old CSS, compiles Sass, combines CSS, minifies CSS', ['clean-css', 'sass'], function() {
bs.notify('<span style="color: grey">Running:</span> CSS task');
return gulp.src('css/*.css')
.pipe(concat('all.min.css'))
.pipe(mincss())
.pipe(gulp.dest('css'))
.pipe(gulp.dest('_site/css'))
.pipe(reload({stream: true}));
});
// -----------------------------------------------------------------------------
// UnCSS Task
//
// Checks the site's usage of Bootstrap and strips unused styles out. Outputs
// the resulting files in the css/ directory where they will be combined and
// minified by a separate task.
//
// Note: this task requires a local server to be running because it references
// the actual compiled site to calculate the unused styles.
// -----------------------------------------------------------------------------
gulp.task('uncss', 'Removes unused CSS from frameworks', function() {
return gulp.src([
'node_modules/bootstrap/dist/css/bootstrap.css',
'node_modules/bootstrap/dist/css/bootstrap-theme.css'
])
.pipe(uncss({
html: [
'http://localhost:3000/',
'http://localhost:3000/audit/',
'http://localhost:3000/foundation/',
'http://localhost:3000/budgets/'
]
}))
.pipe(gulp.dest('css/'));
});
// -----------------------------------------------------------------------------
// Combine and Minify JS
//
// Just like the CSS task, the end result of this task is a minified aggregate
// of JS ready to be served.
// -----------------------------------------------------------------------------
gulp.task('js', 'Combines and minifies JS', function() {
return gulp.src('_js/**/*.js')
.pipe(concat('all.min.js'))
.pipe(uglify())
.pipe(gulp.dest('js'))
.pipe(gulp.dest('_site/js'))
.pipe(reload({stream: true}));
});
// -----------------------------------------------------------------------------
// Generate critical-path CSS
//
// This task generates a small, minimal amount of your CSS based on which rules
// are visible (aka "above the fold") during a page load. We will use a Jekyll
// template command to inline the CSS when the site is generated.
//
// All styles should be directly applying to an element visible when your
// website renders. If the user has to scroll even a small amount, it's not
// critical CSS.
// -----------------------------------------------------------------------------
gulp.task('critical', 'Generates critical CSS for the example site', function () {
return gulp.src('_site/index.html')
.pipe(critical({
dest: '_includes/critical.css',
css: ['css/all.min.css'],
dimensions: [{
width: 320,
height: 480
},{
width: 768,
height: 1024
},{
width: 1280,
height: 960
}],
minify: true,
extract: false
}));
});
// -----------------------------------------------------------------------------
// Minify SVGs and compress images
//
// It's good to maintain high-quality, uncompressed assets in your codebase.
// However, it's not always appropriate to serve such high-bandwidth assets to
// users, in order to reduce mobile data plan usage.
// -----------------------------------------------------------------------------
gulp.task('imagemin', 'Minifies and compresses images on the example site', function() {
return gulp.src('_img/**/*')
.pipe(imagemin({
progressive: true,
svgoPlugins: [{removeViewBox: false}]
}))
.pipe(gulp.dest('img'));
});
// -----------------------------------------------------------------------------
// Jekyll
//
// Regenerate the Jekyll site when files are touched. The --watch flag is not
// used because the bs/watch tasks will handle the "watching" of the files.
// -----------------------------------------------------------------------------
gulp.task('jekyll', 'Development task: generate the Jekyll server', function() {
bs.notify('<span style="color: grey">Running:</span> Jekyll task');
return spawn('bundle', ['exec', 'jekyll', 'build', '--config=_config.yml'], {stdio: 'inherit'})
.on('close', reload);
});
// -----------------------------------------------------------------------------
// Jekyll Serve
//
// This command is used exclusively by Travis to start Jekyll in the background
// then run tests against it.
// -----------------------------------------------------------------------------
gulp.task('jekyll-serve', false, function(callback) {
spawn('bundle', ['exec', 'jekyll', 'serve', '--detach', '--no-watch', '--config=_config.yml'], {stdio: 'inherit'})
.on('close', callback)
});
// -----------------------------------------------------------------------------
// Browser Sync
//
// Makes web development better by eliminating the need to refresh. Essential
// for CSS development and multi-device testing.
// -----------------------------------------------------------------------------
gulp.task('browser-sync', false, function() {
bs({
server: './_site/'
});
});
// -----------------------------------------------------------------------------
// Browser Sync using Proxy server
//
// This is a second (unused) example to demonstrate how you'd connect to a local
// server that runs itself. Examples would be a PHP site such as Wordpress or a
// Drupal site, or a node.js site like Express.
//
// Usage: gulp browser-sync-proxy --port 8080
// -----------------------------------------------------------------------------
gulp.task('browser-sync-proxy', false, function () {
bs({
// Point this to your pre-existing server.
proxy: 'my-local-site.dev' + (u.env.port ? ':' + u.env.port : ''),
// This tells BrowserSync to auto-open a tab once it boots.
open: true
}, function(err, bs) {
if (err) {
console.log(bs.options);
}
});
});
// -----------------------------------------------------------------------------
// Watch tasks
//
// These tasks are run whenever a file is saved. Don't confuse the files being
// watched (gulp.watch blobs in this task) with the files actually operated on
// by the gulp.src blobs in each individual task.
//
// A few of the performance-related tasks are excluded because they can take a
// bit of time to run and don't need to happen on every file change. If you want
// to run those tasks more frequently, set up a new watch task here.
// -----------------------------------------------------------------------------
gulp.task('watch', 'Watch for changes to various files and process them', function() {
gulp.watch('_sass/**/*.scss', ['css']);
gulp.watch('_js/**/*.js', ['js']);
gulp.watch('_img/**/*', ['imagemin']);
gulp.watch(['./**/*.{md,html}', '!./_site/**/*.*', '!./node_modules/**/*.*'], ['jekyll']);
});
// -----------------------------------------------------------------------------
// Convenience task for development.
//
// This is the command you run to warm the site up for development. It will do
// a full build, open BrowserSync, and start listening for changes.
// -----------------------------------------------------------------------------
gulp.task('bs', 'Main development task:', ['css', 'js', /*'imagemin',*/ 'jekyll', 'browser-sync', 'watch']);
// -----------------------------------------------------------------------------
// Performance test: Phantomas
//
// Phantomas can be used to test granular performance metrics. This example
// ensures that the site never exceeds a specific number of HTTP requests.
// -----------------------------------------------------------------------------
gulp.task('phantomas', 'Performance: measure a URL using Phantomas', function() {
var limit = 5;
var phantomas = spawn('./node_modules/.bin/phantomas', ['--url', 'http://localhost:4000', '--assert-requests=' + limit]);
// Uncomment this block to see the full Phantomas output.
// phantomas.stdout.on('data', function (data) {
// data = data.toString().slice(0, -1);
// log('Phantomas:', data);
// });
// Catch any errors.
phantomas.on('error', function (err) {
log(err);
});
// Log results to console.
phantomas.on('close', function (code) {
// Exit status of 0 means success!
if (code === 0) {
log('Phantomas:', c.green('✔︎ Yay! The site makes ' + limit + ' or fewer HTTP requests.'));
}
// Exit status of 1 means the site failed the test.
else if (code === 1) {
log('Phantomas:', c.red('✘ Rats! The site makes more than ' + limit + ' HTTP requests.'));
process.exit(code);
}
// Other exit codes indicate problems with the test itself, not a failed test.
else {
log('Phantomas:', c.bgRed('', c.black('Something went wrong. Exit code'), code, ''));
process.exit(code);
}
});
});
// -----------------------------------------------------------------------------
// Performance test: PageSpeed Insights
//
// Initializes a public tunnel so the PageSpeed service can access your local
// site, then it tests the site. This task outputs the standard PageSpeed results.
//
// The task will output a standard exit code based on the result of the PSI test
// results. 0 is success and any other number is a failure. To learn more about
// bash-compatible exit status codes read this page:
//
// http://tldp.org/LDP/abs/html/exit-status.html
// -----------------------------------------------------------------------------
gulp.task('psi', 'Performance: PageSpeed Insights', function() {
// Set up a public tunnel so PageSpeed can see the local site.
return ngrok.connect(4000, function (err_ngrok, url) {
log(c.cyan('ngrok'), '- serving your site from', c.yellow(url));
// Run PageSpeed once the tunnel is up.
psi.output(url, {
strategy: 'mobile',
threshold: 80
}, function (err_psi, data) {
// Log any potential errors and return a FAILURE.
if (err_psi) {
log(err_psi);
process.exit(1);
}
// Kill the ngrok tunnel and return SUCCESS.
process.exit(0);
});
});
});
// -----------------------------------------------------------------------------
// Performance test: Critical CSS
//
// This test checks our generated critical CSS to make sure there are no external
// requests which would block rendering. Having external calls defeats the entire
// purpose of inlining the CSS, since the external call blocks rendering.
// -----------------------------------------------------------------------------
gulp.task('critical-test', 'Performance: check the contents of critical CSS', function () {
// Spawn our critical CSS test and suppress all output.
var critical = spawn('./examples/critical/critical.sh', ['>/dev/null']);
// Catch any errors.
critical.on('error', function (err) {
log(err);
});
// Log results to console.
critical.on('close', function (code) {
// Exit status of 0 means success!
if (code === 0) {
log('Critical:', c.green('✔︎ Yay! The generated CSS makes zero external requests.'));
}
// Exit status of anything else means the test failed.
else {
log('Critical:', c.red('✘ Rats! The generated CSS makes ' + code + ' external requests.'));
process.exit(code);
}
});
});
// -----------------------------------------------------------------------------
// Performance test: WebPageTest.org
//
// Initializes a public tunnel so the PageSpeed service can access your local
// site, then it tests the site. This task outputs the standard PageSpeed results.
// -----------------------------------------------------------------------------
gulp.task('wpt', 'Performance: WebPageTest.org', function () {
var wpt_test = wpt('www.webpagetest.org', process.env.wptkey);
// Set up a public tunnel so WebPageTest can see the local site.
return ngrok.connect(4000, function (err_ngrok, url) {
log(c.cyan('ngrok'), '- serving your site from', c.yellow(url));
// The `url` variable was supplied by ngrok.
wpt_test.runTest(url, function(err_wpt, data_wpt) {
// Log any potential errors and return a FAILURE.
if (err_wpt) {
log(err_wpt);
process.exit(1);
}
// Open window to results.
var wpt_results = 'http://www.webpagetest.org/result/' + data_wpt.data.testId;
log(c.green('✔︎ Opening results page:', wpt_results));
spawn('open', [wpt_results]);
// Note to developer.
log(c.yellow('⚠️ Please keep this process running until WPT is finished.'));
log(c.yellow('⚠️ Once the results load, hit Control + C to kill this process.'));
});
});
});
// -----------------------------------------------------------------------------
// Performance test: run everything at once
//
// Having a task that simply runs other tasks is nice for Travis or other CI
// solutions, because you only have to specify one command, and gulp handles
// the rest.
// -----------------------------------------------------------------------------
gulp.task('test', 'Run all tests', function (callback) {
sequence(
'jekyll-serve',
'critical-test',
'phantomas',
'psi',
'wpt',
callback
);
});
// -----------------------------------------------------------------------------
// Default: load task listing
//
// Instead of launching some unspecified build process when someone innocently
// types `gulp` into the command line, we provide a task listing so they know
// what options they have without digging into the file.
// -----------------------------------------------------------------------------
gulp.task('default', false, ['help']);