Gulp Fingerprinting + Production Deploys - javascript

I'm using gulp for common tasks on a front-end project. My goal is to fingerprint my JS+CSS assets and update my rev-manifest on deploy without interrupting anything. My tasks look something like this:
gulp.task("clean-js", function() {
return del(["./public/shop-assets/javascripts/production/*.js", "./rev-manifest.json"], function(){
console.log("Production JS folder and rev-manifest JSON cleared");
});
});
gulp.task("clean-css", function() {
return del(["./public/shop-assets/stylesheets/production/*.css", "./rev-manifest-css.json"], function(){
console.log("Production CSS folder and rev-manifest-css JSON cleared");
});
});
gulp.task('js:prod',['clean-js'], function() {
return gulp.src("./public/shop-assets/javascripts/*.js/")
.pipe(uglify())
.pipe(rev())
.pipe(gulp.dest("./public/shop-assets/javascripts/production/"))
.pipe(rev.manifest())
.pipe(gulp.dest("."));
});
gulp.task('css:minify',["clean-css"], function() {
return gulp.src("./public/shop-assets/stylesheets/*.css")
.pipe(minifyCss())
.pipe(rev())
.pipe(gulp.dest("./public/shop-assets/stylesheets/production/"))
.pipe(rev.manifest())
.pipe(rename("rev-manifest-css.json"))
.pipe(gulp.dest("."));
});
This does what I expect-it clears the manifest files each time, then rewrites them. What I would like to do is merely replace the contents of each manifest after I bundled + minified my JS +CSS files without blocking the user experience.
Also, in my express routes file, I am sending the manifest on down so I can then include the scripts/stylesheets like so:
script(src="/shop-assets/javascripts/production/#{assetPathJS}")
But when I delete and rewrite the manifest files, users will see an intermittent failure.
Does anyone have any tips on this matter? Most examples I have seen have only one entrypoint for the gulpfile. Thanks!

I wound up doing something like this in my routes files:
var jsPaths = ServerConfig.assetPaths(["cleanout.js", "hiw.js", "do_good.js"], revManifestJS, "javascripts/");
where that function assetPaths resolves the pathname based on the environment. Simple than I thought.

Related

gulp-rev with useref renaming index.html file as well

I'm new to using gulp and am trying to create a gulpfile for my project.
My first task is to combine all my script and link tags present in index.html and replace them with only a single tag. To achieve this purpose, I'm making use of gulp-useref as below.
gulp.task('useref', ['clean'], function () {
return gulp.src(config.paths.app.src + 'index.html')
.pipe(gulpif('*.js', uglify()))
.pipe(gulpif('*.css', cleanCSS()))
.pipe(useref())
.pipe(gulp.dest(config.paths.dist.src))
});
This thing simply concatenates and minifies all my JS and CSS files and create a single script and link tag for them. All good till here.
Now, I wish to implement hashing to the combined JS and CSS files as well. For this, I'm using gulp-rev and gulp-rev-replace plugin like below.
gulp.task('useref', ['clean'], function () {
return gulp.src(config.paths.app.src + 'index.html')
.pipe(gulpif('*.js', uglify()))
.pipe(gulpif('*.css', cleanCSS()))
.pipe(useref())
.pipe(rev())
.pipe(revReplace())
.pipe(gulp.dest(config.paths.dist.src))
});
This also works good but for one small thing. It creates the hashed filenames not only for my JS and CSS files but for my index.html file as well as below.
Is there a way by which I can avoid the renaming of index.html file while still preserving the JS and CSS hashed filenames because my server would look for the index.html file in the folder rather than index-*********.html?
Thanks.
I'm not sure if this is the ideal way to solve the problem, but my solution was to apply gulpif to filter out css/js assets and then call rev(). revReplace should still update your index file to use the renamed css/js assets.
Essentially you can add the following pipe to your stream:
.pipe(gulpif(*.{js,css}, rev()))
Example based on your code:
gulp.task('useref', ['clean'], function () {
return gulp.src(config.paths.app.src + 'index.html')
.pipe(gulpif('*.js', uglify()))
.pipe(gulpif('*.css', cleanCSS()))
.pipe(useref())
.pipe(gulpif('*.{js,css}', rev()))
.pipe(revReplace())
.pipe(gulp.dest(config.paths.dist.src))
});
I had the same issue, but I was using gulp-rev-all instead of gulp-rev like you are.
My solution (workaround) will still work for you and others that are interested.
Essentially, you need to run another gulp-task after you 'rev' all of your files.
I use gulp-inject to grab the newly revisioned files, and inject them into my index.html file.
Step 1: use gulp-rev or gulp-rev-all in my case to revision your files and save them to your /dist folder.
Example:
gulp.task('rev', function () {
var css= gulp
.src(styles)
.pipe(concat('main.css'))
.pipe(RevAll.revision())
.pipe(gulp.dest('dist/css'));
var js = gulp
.src(scripts)
.pipe(concat('scripts.js'))
.pipe(RevAll.revision())
.pipe(gulp.dest('dist/js'));
return merge(css, js); //merge is from the gulp plugin merge-stream. It allows me to manipulate multiple streams in 1 task instead of having separate tasks.
});
Step 2:
Use gulp-inject to inject the newly created css/js file references into your index.html
Markup your index.html like this:
<!-- inject:css -->
<!-- endinject -->
<!-- inject:js -->
<!-- endinject -->
Step 3:
Use gulp-inject to grab the newly revisioned files and inject them into your index.html
gulp.task('inject',
function() {
var jsSource = gulp.src('./dist/js/*.js', { read: false });
var cssSource = gulp.src('./dist/css/*.css', { read: false });
return gulp.src('./index.html')
.pipe(inject(merge(jsSource, cssSource)))
.pipe(clean({force:true}))
.pipe(gulp.dest('./'));
});
In my project, I do NOT put the index.html in the dist folder. Maybe I should, but in this example/project I do not.
My folder structure
index.html
--/dist
---/js
---/css
---/views
gulp-inject documentation
gulp-rev-all documentation
Please let me know if there are any further questions, I would love to answer them
try
var ignoreIndexHtml = gulpfilter(['**/*', '!**/index.html'], { restore: true });
return gulp
.src('......')
.pipe('......')
.pipe('......')
.pipe(ignoreIndexHtml)
.pipe($.rev())
.pipe(ignoreIndexHtml.restore)
.pipe($.revReplace())
.pipe(gulp.dest('......'));

gulp-useref "additionalStreams" not merging

So I have a simple use case, and it seems very similar to the usecase described in the readme for https://github.com/jonkemp/gulp-useref.
I'm trying to generate a templates.js file with all of the Angular templates during the build. I am trying to do this and NOT have a templates.js file in my local project. So the idea was to merge the output of the template stream into the useref stream so that the resulting scripts.js file would contain all of the files indicated in my index file AND the generated templates ouput.
Here's what I have in the gulp task:
gulp.task('usemin:dist', ['clean:dist'], function() {
var templatesStream = gulp.src([
'./app/**/*.html',
'!./app/index.html',
'!./app/404.html'
]).pipe(templateCache({
module: 'myCoolApp'
}));
var assets = $useref.assets({
additionalStreams: [templatesStream]
});
return gulp.src('./app/index.html')
.pipe(assets)
.pipe(assets.restore())
.pipe($useref())
.pipe(gulp.dest('./dist/'));
});
Now this should allow me to merge the output of the templatesStream and turn it all into one scripts.js file, I think...
I've also tried having <script src="scripts/templates.js"></script> of many forms sitting in my index file to try and assist it. None seem to work.
Anyone else doing this same type of thing? Seems like a common use-case.
I was able to get this to work by looking closely at the test cases.
I now have a templates.js script tag on my index.html file which will 404 while in my local environment.
My gulp task looks like this:
gulp.task('useref:dist', ['clean:dist'], function() {
var templateStream = gulp.src([
'./app/**/*.html',
'!./app/index.html',
'!./app/404.html'
]).pipe(templateCache({
module: 'digitalWorkspaceApp'
}));
var assets = $useref.assets({
additionalStreams: [templateStream]
});
var jsFilter = $filter('**/*.js', {restore: true});
return gulp.src('./app/index.html')
.pipe(assets)
.pipe($useref())
.pipe(gulp.dest('./dist/'));
});
Immediately I can't really see the difference, but it may have all hinged on the addition of this non-existent file in my index.html.

SASS + CSS Injecting with Gulp and BrowserSync

Just getting started with BrowserSync and I have a question regarding the common use-cases on configuration for SASS + CSS injecting.
What I'm a little confused about is weather I can give the server property its own name.
In the docs here they use "./app". Can I use another name like "./site" or am I only allowed to use "./app"?
Here's the code
// task
gulp.task('serve',['sass'], function (){
// static server
browserSync.init({
server: "./app"
});
// watching html and scss files for changes then reloading browser
gulp.watch("app/scss/*.scss", ['sass']);
gulp.watch("app/*.html").on('change', browserSync.reload);
});
UPDATED
If you're looking to set up browserSync as a static server with live reloading you're pretty much right with your approach. You can point browsersync to any folder in your repository using the baseDir option. You can also use multiple directories. For further info check out the options documentation.
First. I would look to split up the tasks you have.
Personally, I would opt to have my server task something like;
gulp.task('serve', ['build'], function(event) {
var server = browsersync.create();
server.init({
baseDir: 'app/'
});
return server.watch('app/**', function(evt, file) {
server.reload();
});
});
Note how build is a dependency. This ensures that all of our served files are in place when we initiate BrowserSync.
Keeping it generic we can then split the SCSS related tasks into their own tasks such as scss:compile and scss:watch.
gulp.task('scss:compile', function(){
return gulp.src('src/scss/**/*.scss')
.pipe(plumber())
.pipe(scss())
.pipe(gulp.dest('app/css/');
});
and
gulp.task('scss:watch', function() {
gulp.watch('src/scss/**/*.scss', ['scss:compile']);
});
For CSS injection. There are two options for doing this
Remember injection differs from reload. Whereas reload will reset the page state, injection won't affect the current page state and justs injects the styles.
Option one is to invoke browsersync.stream() with the piped content of your SASS compilation like follows;
gulp.task('scss:compile', function() {
return gulp.src('src/scss/**/*.scss')
.pipe(plumber())
.pipe(scss())
.pipe(gulp.dest('app/css')
.pipe(browsersync.stream());
});
Note that the last pipe injects the changes. This also requires to check the file type in the BrowserSync watch to only reload when a non CSS file changes.
This option is OK. However, I think it's not clear to have it just bundled on the end there and separate from BrowserSync setup code.
The second option(personally preferred) is to watch for changes with BrowserSync and if a CSS file changes, use vinyl to stream those changes to BrowserSync.
Something like the following tends to work;
var gulp = require('gulp'),
browsersync = require('browser-sync'),
vss = require('vinyl-source-stream'),
vb = require('vinyl-buffer'),
vf = require('vinyl-file');
gulp.task('serve', ['build'], function() {
var server = browsersync.create();
server.init({ baseDir: 'app/' });
return server.watch('app/**', function(evt, file) {
if (evt === 'change' && file.indexOf('.css') === -1)
server.reload();
if (evt === 'change' && file.indexOf('.css') !== -1)
vf.readSync(file)
.pipe(vss(file))
.pipe(vb())
.pipe(server.stream());
});
});
Hope that helps you out!

How can I insert content into a file during a gulp build?

I managed to accomplish my task using a gulp plugin called gulp-insert like this:
gulp.task('compile-js', function () {
// Minify and bundle client scripts.
var scripts = gulp.src([
srcDir + '/routes/**/*.js',
srcDir + '/shared/js/**/*.js'
])
// Sort angular files so the module definition appears
// first in the bundle.
.pipe(gulpAngularFilesort())
// Add angular dependency injection annotations before
// minifying the bundle.
.pipe(gulpNgAnnotate())
// Begin building source maps for easy debugging of the
// bundled code.
.pipe(gulpSourcemaps.init())
.pipe(gulpConcat('bundle.js'))
// Buffer the bundle.js file and replace the appConfig
// placeholder string with a stringified config object.
.pipe(gulpInsert.transform(function (contents) {
return contents.replace("'{{{appConfigObj}}}'", JSON.stringify(config));
}))
.pipe(gulpUglify())
// Finish off sourcemap tracking and write the map to the
// bottom of the bundle file.
.pipe(gulpSourcemaps.write())
.pipe(gulp.dest(buildDir + '/shared/js'));
return scripts.pipe(gulpLivereload());
});
What I'm doing is reading our app's configuration file which is managed by the config module on npm. Getting our config file from server-side code is a snap using var config = require('config');, but we're a single-page app and frequently need access to the configuration settings on the client-side. To do that I stuff the config object into an Angular service.
Here's the Angular service before gulp build.
angular.module('app')
.factory('appConfig', function () {
return '{{{appConfigObj}}}';
});
The placeholder is in a string so that it's valid JavaScript for some of the other gulp plugins that process the file first. The gulpInsert utility lets me insert the config like this.
.pipe(gulpInsert.transform(function (contents) {
return contents.replace("'{{{appConfigObj}}}'", JSON.stringify(config));
}))
This works but feels a little hacky. Not to mention that it has to buffer the whole bundled file just so I can perform the operation. Is there a more elegant way to accomplish the same thing? Preferably one that allows the stream to keep flowing smoothly without buffering the whole bundle at the end? Thanks!
Have you checked gulp-replace-task?
Something like
[...]
.pipe(gulpSourcemaps.init())
.pipe(replace({
patterns: [{
match: '{{{appConfigObj}}}',
replacement: config
}],
usePrefix: false
})
.pipe(gulpUglify())
[...]
Admittedly, this feels a bit hacky, too, but maybe slightly better... I'm using envify and gulp-env in a React project. You could do something like this.
gulpfile.js:
var config = require('config');
var envify = require('envify');
gulp.task('env', function () {
env({
vars: {
APP_CONFIG: JSON.stringify(config)
}
});
});
gulp.task('compile-js', ['env'], function () {
// ... replace `gulp-insert` with `envify`
});
factory:
angular.module('app')
.factory('appConfig', function () {
return process.env.APP_CONFIG;
});

Registering Grunt tasks whose code is located in external JavaScript files

I've written a function which I'd like to use as a Grunt task. I can do this by adding this to the Gruntfile:
grunt.registerTask('foo', function () {
// code here
});
However, it makes more sense to keep the function code in a separate file. I plan to define a bunch of these custom tasks and I don't want to bloat the Gruntfile.
I'm not sure what the preferred way of registering such tasks is. I have found this to work:
grunt.registerTask('foo', function () {
require('./path/to/foo.js')(grunt);
});
So, I'm having the inline function like in the fist example, but this time, I'm loading an external file and invoking it immediately. In that external file, I of course have to write:
module.exports = function (grunt) {
// code here
}
This works, but it feels hackish. Is there a more proper way of doing this?
Short answer: the alternative to this
grunt.registerTask('foo', function () {
require('./path/to/foo.js')(grunt);
});
is http://gruntjs.com/api/grunt#grunt.loadtasks
Long answer:
Normally when you have tasks in external files there are served as other nodejs modules. So, if that is something that you will use in several projects you may want to register it in the registry. Later inside your Gruntfile.js you will have:
grunt.loadNpmTasks('yout-module-here');
The grunt's documentation says:
Load tasks from the specified Grunt plugin. This plugin must be installed locally via npm, and must be relative to the Gruntfile
However, if you don't want to upload anything to the registry you should use loadTasks
grunt.loadTasks('path/to/your/task/directory');
So, once the task is loaded you may use it in your configuration.
Here is a simple grunt task placed in external file:
'use strict';
module.exports = function(grunt) {
grunt.registerMultiTask('nameoftask', 'description', function() {
var self = this;
// this.data here contains your configuration
});
};
And later in Gruntfile.js
grunt.initConfig({
nameoftask: {
task: {
// parameters here
}
}
});
I had a similar problem.
I wanted to modularize my grunt config and custom tasks by functionnalities (big UX/UI blocks) rather than by technical features. AND I wanted to keep the config files next to task files... (better when working on a large legacy codebase with an varied team - 5 persons with varying JS knowledge)
So I externalized my tasks like Krasimir did.
In the gruntfile, I wrote :
//power of globbing for loading tasks
var tasksLocations = ['./grunt-config/default_tasks.js', './grunt-config/**/tasks.js'];
var taskFiles = grunt.file.expand({
filter: "isFile"
}, tasksLocations);
taskFiles.forEach(function(path) {
grunt.log.writeln("=> loading & registering : " + path);
require(path)(grunt);
});
You will find the whole boilerplate gruntfile here (external config and tasks loading) : https://gist.github.com/0gust1/7683132

Categories