S3_website alternatives?

I’ve used the S3_Website gem (https://github.com/laurilehmijoki/s3_website) to sync my site to S3 for years, works great. It is no longer maintained and while I can still use it locally, one site I have is using Gitlab and their CI pipelines and that one no longer works - from what I can tell this is because of 2 things: S3_website will not run on Java 10/11, and the Debian version that Gitlab uses as their runner will not work with Java 8.

At least I think that is what the underlying problems are.

Someone looks to have fixed the version issue on S3_wesbites repo, but I won’t have any way to use that unless it is a gem I can pull in the debian script.

I like S3_website cause it works really well with S3 and cloudfront, and has a lot of configuration options.

Anyone have a suggestion for something similar to S3_website?

I found a couple node packages that will sync to S3 and invalidate items in cloudfront, I added this to a gulp file that I use to handle all my images, JS and SASS (makes jekyll faster).

Works pretty good so far, using gulp 4.

I have cloudfront gzipping files automatically so it is not needed here.

The base gulp command will run jekyll build and launch browsersync and build all the sass and other assets.

gulp deploy will minify the html and then sync with s3 and invalidate CF.

The AWS ID and secret have been removed, they should really be stored somewhere else but it works fine having them in this file.

This is probably an improvement over what I was doing with S3_website, my pingdom score actually went up a couple points. I have also used this with GitLab CI and am able to get it to publish automatically when I make a commit.

So far it works pretty well but I am sure I could improve it some more, not really a gulp expert.

Things this does that are better than jekyll serve

  • css sourcemaps
  • rebuilds/refreshes browser when changes to config file are made
  • auto prefixer for css
  • probably faster as gulp can move image files and process sass faster than jekyll but I have not actually tested that with jekyll 4. It was true for jekyll 3.

The script needs all sass, js and images to be in a folder called _assets, that folder is ignored by jekyll since it starts with an underscore. I then tell jekyll to keep_files: [assets] so it does not delete the assets folder that gulp creates. Kind of a lot going on, I’ll try to make a video on it.

var gulp = require('gulp');
var awspublish = require("gulp-awspublish");
var cloudfront = require('gulp-cloudfront-invalidate-aws-publish');
var htmlmin = require('gulp-htmlmin');
var browsersync = require('browser-sync').create();
var cp          = require('child_process');
var sass = require('gulp-sass');
var postcss    = require('gulp-postcss');
var sourcemaps = require('gulp-sourcemaps');
var autoprefixer = require('autoprefixer');
//var watch = require('gulp-watch'); //not needed?
var terser = require('gulp-terser');
var cssnano = require('cssnano');
var imagemin = require('gulp-imagemin');
var htmlhint = require("gulp-htmlhint");
var parallelize = require("concurrent-transform");

var messages = {
    jekyllBuild: '<span style="color: grey">Running:</span> $ jekyll build'
};

//if sass files change just rebuild them with gulp-sass and what not
function sassRebuild() {
     var plugins = [
        autoprefixer(),
        cssnano()
    ];
     return gulp.src('_assets/sass/**/*.scss')
    .pipe(sourcemaps.init())
    .pipe(sass().on('error', sass.logError))
    .pipe(sourcemaps.init())
    .pipe(postcss(plugins))
    .pipe(sourcemaps.write('.'))
    .pipe( gulp.dest('_site/assets/css/') )
    .pipe(browsersync.stream()); //streams changes to browser without refresh
    
}
function jsRebuild() {
    return gulp.src('_assets/js/**/*.js')
      .pipe(terser()) //uglify with es6
      .pipe( gulp.dest('_site/assets/js/') )     
}

function imagesRebuild() { 
     return gulp.src('_assets/img/**/*.*')
      .pipe( gulp.dest('_site/assets/img/') )
}

//build jekyll
function jekyllBuild(){
  browsersync.notify(messages.jekyllBuild);
  return cp.spawn('jekyll.bat', ['build'], {stdio: 'inherit'})
}

// BrowserSync
function browserSync(done) {
  browsersync.init({
    server: "_site/", logLevel: "silent"
  });
  done();
}

// BrowserSync Reload
function browserSyncReload(done) {
  browsersync.reload();
  done();
}

function watchFiles() {
  gulp.watch("_assets/sass/**/*.scss", sassRebuild);
  gulp.watch("_assets/js/**/*", gulp.series(jsRebuild,browserSyncReload));
  gulp.watch("_assets/images/**/*", gulp.series(imagesRebuild,browserSyncReload));
  gulp.watch(['**/*.*', '!_site/**/*','!_assets/**/*','!node_modules/**/*','!.sass-cache/**/*' ],
    gulp.series(jekyllBuild,browserSyncReload)
  );
}

 // create a new publisher using S3 options
  // http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#constructor-property
var publisher = awspublish.create({
  region: "us-east-1",
  params: {
    Bucket: "prolabprints.com"
  },accessKeyId: 'AK...',             // Optional AWS Access Key ID
  secretAccessKey: 'MZI..v' 
});

  // define custom headers
  var headers = {
    "Cache-Control": "max-age=31536000, no-transform, public"
    // ...
  };

  var cfSettings = {
  distribution: 'E19RZ...', // Cloudfront distribution ID
  accessKeyId: 'AKI...',             // Optional AWS Access Key ID
  secretAccessKey: 'M...v',   
  wait: false,                     // Whether to wait until invalidation is completed (default: false)
  originPath: '',             // Configure OriginPath to be removed of file path to invalidation
  indexRootPath: true             // Invalidate index.html root paths (`foo/index.html` and `foo/`) (default: false)
}
//sync to s3 and invalidate cloudfront, can't have spaces in filenames
function push() {
  console.log('Pushing to S3 and invalidating Cloudfront files');
  return (
    gulp
  .src("./_site/**/*")
  .pipe(parallelize(publisher.publish(headers),10))
  .pipe(publisher.sync())
  .pipe(publisher.cache())
  .pipe(cloudfront(cfSettings))
  .pipe(awspublish.reporter({states: ["create", "update", "delete"]})));
}

//minify html
function minifyHTML() {
    console.log('Minimizing HTML files');
    return gulp.src('_site/**/*.html')
    .pipe(htmlmin({ collapseWhitespace: true, removeComments:true, minifyCSS: true, minifyJS: true }))
    .pipe(gulp.dest(function (file) {
        return file.base;
    }));
}

function validate() {
      return gulp.src('_site/**/*.html')
         .pipe(htmlhint())
         .pipe(htmlhint.reporter());
      }


const watch2 = gulp.parallel(watchFiles,browserSync);
const watch = gulp.parallel(jsRebuild,imagesRebuild,jekyllBuild,sassRebuild,watch2);
const deploy = gulp.series(minifyHTML,push);
const deployGitLab = gulp.series(jsRebuild,imagesRebuild,sassRebuild,minifyHTML,push);

exports.deployGitLab = deployGitLab;
exports.validate = validate;
exports.jekyllBuild = jekyllBuild;
exports.push = push;
exports.minifyHTML = minifyHTML;
exports.watch = watch;
exports.deploy = deploy;
exports.browserSync = browserSync;
exports.sassRebuild = sassRebuild;

gulp.task('default', watch);