Skip to content

Instantly share code, notes, and snippets.

@zspecza
Last active November 23, 2021 07:46
Show Gist options
  • Star 24 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save zspecza/6c1f5b8b6799c04232cc to your computer and use it in GitHub Desktop.
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)
/*=====================================*\
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