Created
October 13, 2016 16:09
-
-
Save pwang2/185e919a8c8f172f7477bc43d4544f79 to your computer and use it in GitHub Desktop.
This file contains 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
var Promise = require('bluebird'); | |
const path = require('path'); | |
const j = path.join; | |
const fs = Promise.promisifyAll(require('fs-extra')); | |
const wiredep = require('wiredep'); | |
const home = require('user-home'); | |
const hashCacheFile = j(home, '.quikr', 'buildCache.json'); | |
const hasher = require('folder-hash'); | |
const _console_log_ = console.log; | |
const _console_info_ = console.info; | |
const _console_warn_ = console.warn; | |
var firstEmit = true; | |
function WatchdogPlugin(options) { | |
this.runner = options.runner; | |
this.runtime = options.runtime; | |
} | |
function reloadHashCache() { | |
var options = { | |
throws: false | |
}; | |
return fs.ensureFileAsync(hashCacheFile) | |
.then(() => fs.readJsonAsync(hashCacheFile, options)) | |
.then(obj => obj || {}); | |
} | |
function getBuildHash(root, w) { | |
return hasher.hashElement(j(root, 'components', w)) | |
.then(hashObj => { | |
return hashObj.hash; | |
}); | |
} | |
function saveHashToFs(root, w) { | |
return reloadHashCache() | |
.then(hashCache => { | |
return getBuildHash(root, w) | |
.then(hash => { | |
if (hash) { | |
hashCache[w] = hash; | |
} | |
}) | |
.then(() => fs.outputJsonAsync(hashCacheFile, hashCache)); | |
}); | |
} | |
function cacheDetected(root, w) { | |
return reloadHashCache() | |
.then(cacheObj => { | |
return getBuildHash(root, w) | |
.then(hash => cacheObj[w] === hash); | |
}); | |
} | |
function getDepPkgs(root, bowerObj) { | |
var wires = wiredep({ | |
directory: j(root, 'bower_components'), | |
devDependencies: true, | |
bowerJson: bowerObj | |
}); | |
return wires.packages || []; | |
} | |
function runNestedBuild(runner, w, env, opts, exported) { | |
opts.s = false; //only entry component is preview mode | |
opts.w = true; //need watch mode to livereload | |
opts.__watchdog = true; //set to trigger nesting build use the shared livereload | |
opts._ = ['build', w]; //just make sure it is consistent | |
return runner(w, env, opts, exported, false) | |
.then(() => { | |
console.log('finish building ' + w); | |
return saveHashToFs(exported.root, w); | |
}); | |
} | |
WatchdogPlugin.prototype.apply = function(compiler) { | |
var self = this; | |
var runner = self.runner; | |
var env = self.runtime.env; | |
var opts = self.runtime.opts; | |
var exported = self.runtime.exported; | |
var root = exported.root; | |
var delayBuilds = []; | |
var preBuilds = []; | |
//watch-run will be triggered only in preview mode | |
compiler.plugin("watch-run", function(compilation, callback) { | |
if (!firstEmit) { | |
callback(); | |
return; | |
} | |
var options = compilation.compiler.options; | |
var namespace = options.namespace; | |
var context = options.context; | |
var bowerObj = require(j(context, 'bower.json')); | |
var pkgs = getDepPkgs(root, bowerObj); | |
var watches = Object.keys(pkgs) | |
.filter(n => n.indexOf(namespace) === 0) | |
.map(n => n.substr(namespace.length + 1)); | |
//weird promise orchestration, but works | |
//tempt using reduce, Promise.reduce, Promise.all, but failed | |
//awful for using global states delayBuilds, preBuilds | |
var ps = watches.map(w => { | |
return cacheDetected(root, w) | |
.then(detected => { | |
if (detected) { | |
delayBuilds.push(w); | |
} else { | |
preBuilds.push(w); | |
} | |
}); | |
}); | |
Promise.all(ps) //resolve all will have the delayBuilds filled | |
.then(() => { | |
return Promise.mapSeries(preBuilds, w => { | |
console.log('===> prebuild ' + w + ' artifacts.'); | |
return runNestedBuild(runner, w, env, opts, exported); | |
}) | |
.then(() => { | |
console.log('===> finish prebuild all'); | |
callback(); | |
}); | |
}); | |
}); | |
//after emitting files for current previewing component | |
//start the build for dependant component, | |
//this makes the dependant watched by webpack, | |
//then changes in dependant component will trigger livereload | |
compiler.plugin('after-emit', function(compilation, callback) { | |
if (firstEmit) { | |
firstEmit = false; | |
delayBuildsAsync(delayBuilds); | |
} | |
callback(); | |
}); | |
function delayBuildsAsync(delayBuilds) { | |
if (!delayBuilds || delayBuilds.length === 0) { | |
return; | |
} | |
setTimeout(function() { | |
console.log('start postponed build'); | |
//we mute the output as it is useless, | |
//we just need to make it watched by webpack | |
//this is pure awful hack again, but works. | |
muteOutput(); | |
//make it sequence, or the output log is messy | |
Promise.mapSeries(delayBuilds, w => { | |
return runNestedBuild(runner, w, env, opts, exported); | |
}) | |
.then(() => { | |
restoreOutput(); | |
console.log('finish postponed build'); | |
delayBuilds = []; | |
}); | |
}, 100); | |
} | |
}; | |
function muteOutput() { | |
console.log = () => {}; | |
console.info = () => {}; | |
console.warn = () => {}; | |
} | |
function restoreOutput() { | |
console.log = _console_log_; | |
console.info = _console_info_; | |
console.warn = _console_warn_; | |
} | |
module.exports = WatchdogPlugin; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment