Last active
October 17, 2019 00:17
-
-
Save ozanmuyes/3ee9dcebf3843f6500da67f5002bb396 to your computer and use it in GitHub Desktop.
gulpfile for Gulp 4 to ease development of website with Twig, SCSS and Babel
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
const fs = require('fs'); | |
const path = require('path'); | |
const gulp = require('gulp'); | |
const Mem = require('gulp-mem'); | |
const data = require('gulp-data'); | |
const JSON5 = require('json5'); | |
const twig = require('gulp-twig'); | |
const bs = require('browser-sync').create(); | |
const sass = require('gulp-sass'); | |
const sourcemaps = require('gulp-sourcemaps'); | |
const postcss = require('gulp-postcss'); | |
const autoprefixer = require('autoprefixer'); | |
const cssnano = require('cssnano'); | |
const babel = require('gulp-babel'); | |
const concat = require('gulp-concat'); | |
const rimraf = require('rimraf'); | |
const buildOutput = './dist'; // Relative to project root | |
const paths = { | |
templates: { | |
input: './src/templates', | |
extension: 'twig', | |
output: buildOutput // Put compiled HTML files to server root (e.g. '/index.html') | |
}, | |
data: { | |
input: './src/data', | |
extension: 'json5', | |
// no output as the data merged with a template | |
}, | |
styles: { | |
input: './src/styles', | |
extension: 'scss', | |
output: `${buildOutput}/public/css` | |
}, | |
scripts: { | |
input: './src/scripts', | |
extension: 'js', | |
output: `${buildOutput}/public/js`, | |
outputFilename: 'all' // Concatenated JS file name (e.g. '/dist/public/js/all.js') | |
}, | |
}; | |
// To simulate Apache/nginx URL-rewrite on development server | |
const routes = { | |
// NOTE No need to add for index page (i.e. `'/': '/index.html'`) - unless an 'index.html' is created (via templating engine) | |
'/about': '/about.html', | |
'/products/foo': '/product.html', | |
// | |
}; | |
const routeKeys = Object.keys(routes); | |
sass.compiler = require('node-sass'); | |
const mem = new Mem(); | |
mem.serveBasePath = buildOutput; | |
const compileTemplates = () => gulp | |
.src(`${paths.templates.input}/*.${paths.templates.extension}`, { | |
since: gulp.lastRun(compileTemplates) | |
}) | |
.pipe(data((file) => { | |
const dataFilepath = `${paths.data.input}/${path.basename(file.path)}.${paths.data.extension}`; | |
return (fs.existsSync(dataFilepath)) | |
? JSON5.parse(fs.readFileSync(dataFilepath)) | |
: {}; | |
})) | |
.pipe(twig()) | |
.pipe(mem.dest(paths.templates.output)); | |
const compileStyles = () => gulp | |
.src(`${paths.styles.input}/**/*.${paths.styles.extension}`) | |
.pipe(sass()) | |
.pipe(mem.dest(paths.styles.output)) | |
.pipe(bs.stream()); // inject CSS w/o reloading the page | |
const cleanStyles = () => rimraf(`${paths.styles.output}/*`); | |
const compileScripts = () => gulp | |
.src(`${paths.scripts.input}/**/*.${paths.scripts.extension}`) | |
.pipe(babel({ presets: ['@babel/env'] })) | |
.pipe(concat(`${paths.scripts.outputFilename}.${paths.scripts.extension}`)) | |
.pipe(mem.dest(paths.scripts.output)); | |
const cleanScripts = () => rimraf(`${paths.scripts.output}/*`); | |
const reload = (done) => { | |
bs.reload(); | |
done(); // Without this subsequent changes on watched files won't trigger reload | |
}; | |
const watch = () => { | |
gulp.watch(`${paths.templates.input}/**/*.${paths.templates.extension}`, gulp.series(compileTemplates, reload)); | |
gulp.watch(`${paths.data.input}/*.${paths.data.extension}`, gulp.series(compileTemplates, reload)); | |
gulp.watch(`${paths.styles.input}/**/*.${paths.styles.extension}`, gulp.series(compileStyles)); // NOTE No `reload` here thanks to `bs.stream()` | |
gulp.watch(`${paths.scripts.input}/**/*.${paths.scripts.extension}`, gulp.series(compileScripts, reload)); | |
}; | |
const serve = gulp.series(gulp.parallel(compileTemplates, compileStyles, compileScripts), () => { | |
bs.init({ | |
serveStatic: [ | |
{ | |
route: '/public', // URL prefix for HTML and CSS | |
dir: './public' // Directory on the filesystem that houses static content | |
} | |
], | |
server: { | |
baseDir: buildOutput, | |
middleware: [ | |
(req, _, next) => { | |
if (routeKeys.includes(req.url)) { // TODO What about regex keys? | |
req.url = routes[req.url]; // rewrite URL | |
} | |
next(); | |
}, | |
mem.middleware, | |
], | |
} | |
}); | |
}); | |
const compileStylesForProduction = () => gulp | |
.src(`${paths.styles.input}/**/*.${paths.styles.extension}`) | |
.pipe(sourcemaps.init()) | |
.pipe(sass()) // No need to set `outputStyle` thanks to cssnano | |
.pipe(postcss([ | |
autoprefixer(), | |
cssnano(), | |
])) | |
.pipe(sourcemaps.write('.')) | |
.pipe(gulp.dest(paths.styles.output)); | |
const compileScriptsForProduction = () => gulp | |
.src(`${paths.scripts.input}/**/*.${paths.scripts.extension}`) | |
.pipe(sourcemaps.init()) | |
.pipe(babel({ presets: ['@babel/env'] })) | |
.pipe(concat(`${paths.scripts.outputFilename}.${paths.scripts.extension}`)) | |
.pipe(sourcemaps.write('.')) | |
.pipe(gulp.dest(paths.scripts.output)); | |
const clean = (done) => rimraf(path.join(__dirname, buildOutput), done); | |
const compile = gulp.series(gulp.parallel(compileStylesForProduction, compileScriptsForProduction)); | |
// NOTE This is for (production) build. There is no equivalent for this task | |
// for development simply because BrowserSync development server was | |
// setup to proxy '/public' URLs to 'ROOT/public' directory. | |
const copyPublicAsIs = () => gulp | |
.src('./public/**/*') | |
.pipe(gulp.dest(`${buildOutput}/public`)); | |
module.exports = { | |
default: gulp.parallel(serve, watch), | |
'compile:styles': gulp.series(cleanStyles, compileStylesForProduction), | |
'compile:scripts': gulp.series(cleanScripts, compileScriptsForProduction), | |
clean, | |
compile, | |
build: gulp.series(clean, copyPublicAsIs, compile), | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment