I used this in gulp4 to work around the issue.
Usage:
import gulp from 'gulp'
import gls from 'gulp-live-server'
import { build } from './build'
import { scan } from './ghetto-watch'
let _server = gls(['-r', 'babel-register', 'index.js'])
function start (callback) {
_server.start()
callback()
}
function stop (callback) {
_server.stop()
if (callback) callback()
}
function notify (callback) {
_server.notify()
callback()
}
function watch () {
let src = [
'*.js',
'api/**/*',
'site/**/*'
]
let scanner = scan(src)
let full = gulp.series(
scanner.stop.bind(scanner),
build,
notify,
stop,
start,
scanner.start.bind(scanner)
)
scanner.on('changed', (changed) => {
console.log('changed:', changed)
full()
})
scanner.start()
}
const run = gulp.series(build, start, watch)
gulp.task('start', run)
gulp.task('default', run)
// ghetto-watch.js
import fs from 'fs'
import path from 'path'
import map from 'async/map'
import mm from 'micromatch'
import { EventEmitter } from 'events'
// This should not exist but docker volumes for windows do not trigger file system events
// So instead I scan all the files looking for modify time changes and manually trigger events
let cache = {}
let token = null
function flatten(arr) {
return arr.reduce(function (flat, toFlatten) {
return flat.concat(Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten);
}, []);
}
function scanFiles (file, callback) {
fs.lstat(file, (err, stat) => {
if (err) return callback(err)
if (stat.isDirectory()) {
let dir = file
fs.readdir(dir, (err, files) => {
if (err) return callback(err)
let resolved = files.map(f => path.join(dir, f))
map(resolved, scanFiles, (err, results) => {
if (err) return callback(err)
let changed = flatten(results.filter(f => f))
callback(null, changed)
})
})
} else if (stat.isFile()) {
let mtime = stat.mtime.getTime()
let changed = cache[file] != null && cache[file] !== mtime
cache[file] = mtime
callback(null, changed ? file : null)
} else {
callback()
}
})
}
export class Scanner extends EventEmitter {
constructor (src) {
super()
this.running = false
this.src = src.map(p => path.resolve(p))
}
scan (file, callback) {
if (!this.running) return callback()
fs.lstat(file, (err, stat) => {
if (err) return callback(err)
if (stat.isDirectory()) {
let dir = file
fs.readdir(dir, (err, files) => {
if (err) return callback(err)
let resolved = files.map(f => path.join(dir, f))
map(resolved, scanFiles, (err, results) => {
if (err) return callback(err)
let changed = flatten(results.filter(f => f))
callback(null, changed)
})
})
} else if (stat.isFile()) {
let mtime = stat.mtime.getTime()
let changed = cache[file] != null && cache[file] !== mtime
cache[file] = mtime
callback(null, changed ? file : null)
} else {
callback()
}
})
}
run () {
if (this.running) {
this.scan(process.cwd(), (err, changed) => {
if (err) console.error(err)
if (mm(changed, this.src).length) this.emit('changed') // a change matching src glob happened!
setTimeout(() => this.run(), 0)
})
}
}
start (callback) {
if (!this.running) {
this.running = true
this.run()
}
if (callback) callback()
}
stop (callback) {
this.running = false
if (callback) callback()
}
}
export function scan (src) {
return new Scanner(src)
}