Last active
November 23, 2021 07:46
-
-
Save zspecza/6c1f5b8b6799c04232cc to your computer and use it in GitHub Desktop.
Live inject CSS, Javascript & HTML with BrowserSync, Watchify, Amok & Gulp (excuse the messiness, still a WIP)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*=====================================*\ | |
build tasks | |
\*=====================================*/ | |
/** | |
* This gulpfile is optimised for developing | |
* React.js apps in ES6 through Babel, and is | |
* designed to live-inject CSS, HTML and even JavaScript | |
* changes so maintaining state in an application when | |
* editing code is super easy. | |
*/ | |
// require all dependencies | |
var url = require('url'); | |
var fs = require('fs'); | |
var path = require('path'); | |
var gulp = require('gulp'); | |
var source = require('vinyl-source-stream'); | |
var buffer = require('vinyl-buffer'); | |
var axis = require('axis'); | |
var modernizrStylus = require('modernizr-stylus'); | |
var browserSync = require('browser-sync'); | |
var rupture = require('rupture'); | |
var gutil = require('gulp-util'); | |
var plugins = require('gulp-load-plugins')(); | |
var browserify = require('browserify'); | |
var watchify = require('watchify'); | |
var babelify = require('babelify'); | |
var _ = require('lodash'); | |
var run = require('run-sequence'); | |
var envify = require('envify'); | |
var del = require('del'); | |
var lost = require('lost'); | |
var autoprefixer = require('autoprefixer'); | |
var htmlInjector = require('bs-html-injector'); | |
var spawn = require('child_process').spawn; | |
var reload = browserSync.reload; | |
/** | |
* utility function that takes in an error, makes the OS beep and | |
* prints the error to the console | |
*/ | |
var onError = function(error) { | |
gutil.beep(); | |
gutil.log(error.message); | |
browserSync.notify(error.message); | |
}; | |
/** | |
* there are key differences between running a task in normal mode versus | |
* production mode. this flag detects whether or not this is a production | |
* build. when building for production, it is important to append the argument | |
* `--production` to the respective gulp command you are trying to execute, | |
* e.g. `$ gulp build --production` | |
*/ | |
var production = gutil.env.production; | |
// initialise file path store | |
var PATHS = {}; | |
// change folder structure here | |
PATHS.ROOT = '.'; | |
PATHS.ASSET_DIR = path.join(PATHS.ROOT, 'assets'); | |
PATHS.DST_DIR = path.join('public'); | |
PATHS.CSS_SRC = path.join(PATHS.ASSET_DIR, 'css'); | |
PATHS.CSS_DST = path.join(PATHS.DST_DIR, 'css'); | |
PATHS.JS_SRC = path.join(PATHS.ASSET_DIR, 'js'); | |
PATHS.JS_DST = path.join(PATHS.DST_DIR, 'js'); | |
PATHS.IMAGES_SRC = path.join(PATHS.ASSET_DIR, 'img'); | |
PATHS.IMAGES_DST = path.join(PATHS.DST_DIR, 'img'); | |
PATHS.VIEWS_SRC = path.join('views'); | |
PATHS.VIEWS_DST = path.join(PATHS.DST_DIR); | |
/** | |
* @task clean | |
* cleans the destination directory of old files | |
*/ | |
gulp.task('clean', function(done) { | |
del([PATHS.DST_DIR], done); | |
}); | |
/** | |
* @task browser-sync | |
* this task starts a development server that live-reloads the browser | |
* when files change. it also serves as a means for multi-device testing. | |
*/ | |
gulp.task('browser-sync', function() { | |
var settings = { | |
server: { | |
baseDir: PATHS.DST_DIR | |
}, | |
}; | |
// we don't want to open the browser-sync server | |
// amok will open a browser through chrome's remote debugger | |
settings.open = false; | |
browserSync.use(htmlInjector); | |
return browserSync(settings); | |
}); | |
/** | |
* @task styles | |
* this task compiles stylus preprocessor files to CSS and automatically | |
* vendor prefixes all properties, as well as generates sourcemaps. if you | |
* pass the optional `--production` flag, it will forego sourcemaps and | |
* minify the file with the addition of structural optimisation. additionally, | |
* it includes various plugins and postcss transforms to ease development. | |
* the plugins are as follows: | |
* | |
* axis - a terse, modular CSS framework | |
* rupture - media queries on steroids | |
* modernizr-stylus - helpers to ease writing css feature detection selectors | |
* lost - a pretty freaking awesome grid system by Cory Simmons | |
*/ | |
gulp.task('styles', function() { | |
var options = { | |
use: [axis(), rupture(), modernizrStylus()], | |
url: { | |
name: 'embedurl', | |
limit: 'false', | |
paths: [PATHS.IMAGES_DST] | |
} | |
}; | |
options['include css'] = true; | |
return gulp.src([path.join(PATHS.CSS_SRC, 'index.styl')]) | |
.pipe(plugins.plumber()) | |
.pipe(plugins.stylus(production ? options : _.extend(options, { | |
sourcemap: { | |
inline: true, | |
sourceRoot: PATHS.ROOT, | |
basePath: PATHS.CSS_DST | |
} | |
}))) | |
.pipe(production ? gutil.noop() : plugins.sourcemaps.init({ | |
loadMaps: true | |
})) | |
.pipe(plugins.postcss([lost(), autoprefixer()])) | |
.pipe(production ? plugins.csso() : gutil.noop()) | |
.pipe(production ? gutil.noop() : plugins.sourcemaps.write('.', { | |
includeContent: false, | |
sourceRoot: PATHS.ROOT | |
})) | |
.pipe(plugins.size({ showFiles: true })) | |
.pipe(gulp.dest(PATHS.CSS_DST)) | |
.pipe(plugins.filter('**/*.css')) | |
.pipe(reload({ stream: true })); | |
}); | |
/** | |
* @task images | |
* simply optimises gif, jpg, png and svg assets for slightly smaller filesize. | |
* caches unchanged images to make the task run a little faster | |
*/ | |
gulp.task('images', function() { | |
return gulp.src(path.join(PATHS.IMAGES_SRC, '**', '*.*')) | |
.pipe(plugins.plumber()) | |
.pipe(plugins.cached('image-optimisation', { optimizeMemory: true })) | |
.pipe(production ? plugins.imagemin() : gutil.noop()) | |
.pipe(plugins.size({ showFiles: true })) | |
.pipe(gulp.dest(PATHS.IMAGES_DST)) | |
.pipe(reload({ stream: true })) | |
}); | |
/** | |
* delegates javascript build tasks, depending on production | |
* vs dev mode, this function will determine whether to use watchify | |
* to cache for incremental building. it also registers all of | |
* the necessary browserify transforms and minifies code in | |
* production builds. | |
*/ | |
var scripts = function(options) { | |
var settings = _.extend({ | |
watching: false, | |
which: 'index.js' | |
}, options); | |
var bundler = browserify( | |
path.join(__dirname, PATHS.JS_SRC, settings.which), { | |
basedir: __dirname, | |
debug: !production, | |
insertGlobals: false, | |
cache: {}, | |
packageCache: {}, | |
fullPaths: settings.watching, | |
noparse: ['lodash'] | |
} | |
); | |
if (settings.watching) bundler = watchify(bundler); | |
bundler.transform(babelify.configure({})); | |
bundler.transform('brfs'); | |
bundler.transform('debowerify'); | |
bundler.transform('decomponentify'); | |
bundler.transform('deamdify'); | |
bundler.transform('envify'); | |
var rebundle = function() { | |
return bundler.bundle() | |
.on('error', onError) | |
.pipe(source(settings.which)) | |
.pipe(buffer()) | |
.pipe(production ? gutil.noop() : plugins.sourcemaps.init({ | |
loadMaps: true | |
})) | |
.pipe(production ? plugins.uglify() : gutil.noop()) | |
.pipe(production ? gutil.noop() : plugins.sourcemaps.write('.')) | |
.pipe(plugins.size({ showFiles: true })) | |
.pipe(gulp.dest(PATHS.JS_DST)); | |
}; | |
bundler.on('update', rebundle); | |
return rebundle(); | |
}; | |
/** | |
* build application logic into a bundle | |
*/ | |
gulp.task('scripts', function() { | |
return scripts({ | |
watching: false, | |
which: 'index.js' | |
}); | |
}); | |
/** | |
* build and watch application logic for changes | |
*/ | |
gulp.task('watch-scripts', function() { | |
return scripts({ | |
watching: true, | |
which: 'index.js' | |
}); | |
}); | |
/** | |
* build vendor lib | |
*/ | |
gulp.task('scripts-vendor', function() { | |
return scripts({ | |
watching: false, | |
which: 'vendor.js' | |
}); | |
}); | |
/** | |
* build and watch vendor files for changes (unlikely) | |
*/ | |
gulp.task('watch-scripts-vendor', function() { | |
return scripts({ | |
watching: true, | |
which: 'vendor.js' | |
}); | |
}); | |
/** | |
* @task views | |
* simply copies html from `views` to the destination directory | |
*/ | |
gulp.task('views', function() { | |
return gulp.src(path.join(PATHS.VIEWS_SRC, '**', '*.html')) | |
.pipe(gulp.dest(PATHS.DST_DIR)) | |
.pipe(plugins.size({ showFiles: true })); | |
}); | |
/** | |
* ensures certain tasks are run in sequence, this is just for internal use | |
*/ | |
var startTasks = function(options, done) { | |
var concurrentTasks = ['styles', 'images', 'views']; | |
var args; | |
if (options.watch) { | |
concurrentTasks.push('watch-scripts-vendor'); | |
concurrentTasks.push('watch-scripts'); | |
args = ['clean', concurrentTasks, 'browser-sync']; | |
} else { | |
concurrentTasks.push('scripts-vendor'); | |
concurrentTasks.push('scripts'); | |
args = ['clean', concurrentTasks]; | |
} | |
args.push(done); | |
run.apply(this, args); | |
}; | |
/** | |
* compiles everything | |
*/ | |
gulp.task('build', function(done) { | |
startTasks({ watch: false }, done); | |
}); | |
/** | |
* compiles everything, watches for changes | |
*/ | |
gulp.task('watch-build', function(done) { | |
startTasks({ watch: true }, done); | |
}); | |
/** | |
* starts a development server, watches files for changes, runs tasks, | |
* spawns Amok.js for live javascript injection | |
*/ | |
gulp.task('watch', ['watch-build'], function() { | |
gulp.watch(path.join(PATHS.CSS_SRC, '**/*.styl'), ['styles']); | |
gulp.watch(path.join(PATHS.IMAGES_SRC, '**/*.*'), ['images']); | |
gulp.watch(path.join(PATHS.VIEWS_SRC, '**/*.html'), ['views']); | |
gulp.watch(path.join(PATHS.VIEWS_DST, '**/*.html'), htmlInjector); | |
var amok = spawn('../node_modules/.bin/amok', [ | |
'--client', | |
'chrome', | |
'http://localhost:3000', | |
'js/build.js', | |
'--verbose' | |
], { | |
cwd: PATHS.DST_DIR | |
}); | |
amok.stdout.on('data', function(data) { | |
gutil.log('Amok: ' + data.toString('utf8')); | |
}); | |
amok.stderr.on('data', function(data) { | |
gutil.log('Amok: ' + data.toString('utf8')); | |
}); | |
amok.on('close', gutil.log.bind(gutil, 'Amok exited with code:')); | |
}); | |
/** | |
* alias to `gulp watch` | |
*/ | |
gulp.task('default', ['watch']); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment