hot-reload
Advanced tools
Comparing version 1.2.1 to 1.2.2
@@ -8,305 +8,487 @@ // third-party dependencies | ||
function resolveFilename(filename) { | ||
if (filename.constructor !== String) { | ||
return filename; | ||
} | ||
var simpleRegExpReplacements = { | ||
"*": ".*?", | ||
"?": ".?" | ||
}; | ||
var simpleRegExpTest = /[\?\*]/; | ||
function isSimpleRegExp(str) { | ||
return simpleRegExpTest.test(str); | ||
} | ||
function escapeRegExpStr(str) { | ||
return str.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&"); | ||
} | ||
function createSimpleRegExp(str) { | ||
var _this = this; | ||
if ((filename.charAt(0) !== '.') && (filename.charAt(0) !== '/')) { | ||
return filename; | ||
} else { | ||
return path.resolve(filename); | ||
} | ||
return new RegExp("^" + str.replace(/[\*\?]|[^\*\?]*/g, function(match) { | ||
return simpleRegExpReplacements[match] || escapeRegExpStr(match); | ||
}) + "$"); | ||
} | ||
function HotReloader(config) { | ||
var self = this; | ||
events.EventEmitter.call(this); | ||
function createSimpleRegExpFilter(str, matchResult) { | ||
var simpleRegExp = createSimpleRegExp(str); | ||
var lastReloadTimestamp = Date.now(); | ||
return function(str) { | ||
//console.error('str ', str, simpleRegExp); | ||
return simpleRegExp.test(str) ? matchResult || true : false; | ||
} | ||
} | ||
if (config) { | ||
if (config.include) { | ||
this.include(config.include); | ||
console.log('[Hot Reload] Watched directories/files: ' + JSON.stringify(this._includes)); | ||
function getMatch(filters, path) { | ||
for (var i=0, len=filters.length; i<len; i++) { | ||
var result = filters[i](path); | ||
if (result !== false) { | ||
return result; | ||
} | ||
} | ||
return undefined; | ||
} | ||
if (config.exclude) { | ||
this.exclude(config.exclude); | ||
console.log('[Hot Reload] Exclude from watched directories: ' + JSON.stringify(this._excludes)); | ||
function getMatches(filters, path) { | ||
var matches = []; | ||
for (var i=0, len=filters.length; i<len; i++) { | ||
var result = filters[i](path); | ||
if (result !== false) { | ||
matches.push(result); | ||
} | ||
} | ||
return matches; | ||
} | ||
if (config.alwaysReload) { | ||
this.alwaysReload(config.alwaysReload); | ||
console.log('[Hot Reload] Always reload: ' + JSON.stringify(this._alwaysReload)); | ||
} | ||
function hasMatch(filters, path) { | ||
return getMatch(filters, path) !== undefined ? true : false; | ||
} | ||
if (config.neverReload) { | ||
this.neverReload(config.neverReload); | ||
console.log('[Hot Reload] Never reload: ' + JSON.stringify(this._neverReload)); | ||
function HotReloader(require) { | ||
events.EventEmitter.call(this); | ||
var _this = this; | ||
this._require = require; | ||
this._uncacheIncludes = []; | ||
this._uncacheExcludes = []; | ||
this._watchIncludes = {}; | ||
this._watchExcludeFilters = []; | ||
this._reloadIncludes = []; | ||
this._reloadExcludes = []; | ||
this._specialReloadIncludes = []; | ||
this._specialReloadExcludes = []; | ||
this._pending = 0; | ||
this._reloadDelay = 2000; | ||
this._lastReloadTime = null; | ||
this._handleComplete = function() { | ||
if (--_this._pending === 0) { | ||
_this.emit('ready'); | ||
} | ||
} | ||
} | ||
/** | ||
* Reloads all reloadable modules when a watched file is changed. | ||
*/ | ||
this.reload = function() { | ||
self._reloadTimeout = null; | ||
util.inherits(HotReloader, events.EventEmitter); | ||
var now = Date.now(); | ||
var diff = now - lastReloadTimestamp; | ||
if (diff <= 500) { | ||
HotReloader.prototype._createFilterFunc = function(filter) { | ||
} | ||
HotReloader.prototype._addModuleFilters = function(target, args) { | ||
for (var i=0, len=args.length; i<len; i++) { | ||
var arg = args[i]; | ||
if (Array.isArray(arg)) { | ||
this._addModuleFilters(target, arg); | ||
return; | ||
} | ||
else { | ||
self.emit('beforeReload'); | ||
var reloadableModules = []; | ||
var filter = arg; | ||
var filterFunc; | ||
// FIRST PASS: loop through the module cache and remove entries within directories that we are watching | ||
for (var key in require.cache) { | ||
var module = require.cache[key]; | ||
if (typeof filter === 'string') { | ||
if (isSimpleRegExp(filter)) { | ||
filterFunc = createSimpleRegExpFilter(filter); | ||
} | ||
else { | ||
if (self.isModuleReloadable(module)) { | ||
//console.log('[Hot Reload] Unloading ' + module.filename); | ||
// delete the cache entry only in first pass | ||
delete require.cache[key]; | ||
var moduleUri = this._require.resolve(filter); | ||
filterFunc = function(input) { | ||
return moduleUri === input; | ||
} | ||
} | ||
} | ||
else if (filter.constructor === RegExp) { | ||
filterFunc = function(testModule) { | ||
return testModule.test(filter); | ||
} | ||
} | ||
else if (typeof filter === 'function') { | ||
filterFunc = filter; | ||
} | ||
else { | ||
throw new Error("Invalid module filter: " + filter); | ||
} | ||
// keep track of the modules that | ||
reloadableModules.push(module); | ||
} else { | ||
//console.log('[Hot Reload] Not unloading ' + module.filename); | ||
} | ||
target.push(filterFunc); | ||
} | ||
} | ||
} | ||
console.log('[Hot Reload] ' + reloadableModules.length + ' modules removed from cache.'); | ||
HotReloader.prototype.uncache = function(filter) { | ||
this._addModuleFilters(this._uncacheIncludes, arguments); | ||
return this; | ||
} | ||
if (!config || (config.autoReload !== false)) { | ||
// SECOND PASS: Now trigger a reload of all of the modules since their cache entry was removed | ||
for (var i = 0; i < reloadableModules.length; i++) { | ||
// reload the module | ||
self.reloadModule(reloadableModules[i]); | ||
} | ||
} | ||
HotReloader.prototype.uncacheExclude = function(filter) { | ||
this._addModuleFilters(this._uncacheExcludes, arguments); | ||
return this; | ||
} | ||
self.emit('afterReload'); | ||
HotReloader.prototype.reload = function(filter) { | ||
this._addModuleFilters(this._reloadIncludes, arguments); | ||
return this; | ||
} | ||
lastReloadTimestamp = now; | ||
} | ||
HotReloader.prototype.reloadExclude = function(filter) { | ||
this._addModuleFilters(this._reloadExcludes, arguments); | ||
return this; | ||
} | ||
util.inherits(HotReloader, events.EventEmitter); | ||
HotReloader.prototype.watch = function(dir, recursive) { | ||
var _this = this; | ||
var watchIncludes = this._watchIncludes; | ||
function matches(filter, name) { | ||
if (filter.constructor === RegExp) { | ||
// see if name matches regex | ||
if (filter.test(name)) { | ||
return true; | ||
} | ||
} else { | ||
// see if name starts with given filter value | ||
if (name.indexOf(filter) === 0) { | ||
return true; | ||
} | ||
function callback(path, eventArgs) { | ||
watchIncludes[path] = {path: path, stat: eventArgs.stat}; | ||
} | ||
} | ||
HotReloader.prototype.isModuleReloadable = function(module) { | ||
this._pending++; | ||
var filename = module.filename; | ||
directoryWalker.create() | ||
.recursive(recursive) | ||
.onDirectory(callback) | ||
.onRoot(callback) | ||
.onError(function(e) { | ||
console.error('Directory walk error: ', e); | ||
}) | ||
.onComplete(this._handleComplete) | ||
.walk(dir); | ||
if (this._excludes) { | ||
for (var i = 0; i < this._excludes.length; i++) { | ||
var exclude = this._excludes[i]; | ||
if (matches(exclude, filename)) { | ||
return false; | ||
} | ||
return this; | ||
} | ||
HotReloader.prototype._addPathFilters = function(target, filter, recursive, matchResult) { | ||
if (Array.isArray(filter)) { | ||
var filters = filter; | ||
for (var i=0, len=filters.length; i<filters; i++) { | ||
this._addPathFilters(target, filters[i], recursive, matchResult); | ||
} | ||
return; | ||
} | ||
if (this._neverReload) { | ||
for (var i = 0; i < this._neverReload.length; i++) { | ||
var exclude = this._neverReload[i]; | ||
if (matches(exclude, filename)) { | ||
return false; | ||
var filterFunc; | ||
if (typeof filter === 'string') { | ||
if (isSimpleRegExp(filter)) { | ||
filterFunc = createSimpleRegExpFilter(filter, matchResult); | ||
} | ||
else { | ||
var path = fs.realpathSync(filter); | ||
if (fs.existsSync(path)) { | ||
var stat = fs.statSync(path); | ||
if (stat.isFile()) { | ||
recursive = false; | ||
} | ||
} | ||
recursive = recursive !== false; | ||
filterFunc = function(testPath) { | ||
var match = recursive ? testPath.startsWith(path) : testPath === path; | ||
return match ? matchResult || true : false; | ||
}; | ||
} | ||
} | ||
if (this._alwaysReload) { | ||
for (var i = 0; i < this._alwaysReload.length; i++) { | ||
var include = this._alwaysReload[i]; | ||
if (matches(include, filename)) { | ||
return true; | ||
} | ||
else if (filter.constructor === RegExp) { | ||
filterFunc = function(testPath) { | ||
return testPath.test(filter) ? matchResult || true : false; | ||
} | ||
} | ||
if (this._includes) { | ||
for (var i = 0; i < this._includes.length; i++) { | ||
var include = this._includes[i]; | ||
if (matches(include, filename)) { | ||
return true; | ||
else if (typeof filter === 'function') { | ||
if (matchResult) { | ||
filterFunc = function(path) { | ||
var result = filter(path); | ||
return result ? matchResult : false; | ||
} | ||
} | ||
else { | ||
filterFunc = filter; | ||
} | ||
} | ||
else { | ||
throw new Error("Invalid filter: " + filter + " (" + (typeof filter) + ")"); | ||
} | ||
return false; | ||
target.push(filterFunc); | ||
return this; | ||
} | ||
/** | ||
* Reload a given module. | ||
* @param {Module} module a module | ||
* @param {EventEmitter} an event emitter | ||
*/ | ||
HotReloader.prototype.reloadModule = function(module) { | ||
console.log('[Hot Reload] Reloading module: ' + module.filename); | ||
this.emit('beforeModuleReload', module); | ||
HotReloader.prototype.watchExclude = function(filter, recursive) { | ||
this._addPathFilters(this._watchExcludeFilters, filter, recursive); | ||
return this; | ||
} | ||
if (module.exports.unloadModule) { | ||
module.exports.unloadModule.call(exports); | ||
HotReloader.prototype.specialReload = function(filter, recursive, handlerFunc) { | ||
if (arguments.length === 2) { | ||
handlerFunc = arguments[1]; | ||
recursive = true; | ||
} | ||
delete require.cache[module.filename]; | ||
var newModule = require(module.filename); | ||
this._addPathFilters(this._specialReloadIncludes, filter, recursive, handlerFunc); | ||
return this; | ||
} | ||
// copy properties from new module to old module in case their are some | ||
// references to old module | ||
for (var key in newModule) { | ||
if (newModule.hasOwnProperty(key)) { | ||
module.exports[key] = newModule[key]; | ||
} | ||
} | ||
this.emit('afterModuleReload', module); | ||
HotReloader.prototype.specialReloadExclude = function(filter, recursive) { | ||
this._addPathFilters(this._specialReloadExcludes, filter, recursive); | ||
return this; | ||
} | ||
HotReloader.prototype.watch = function(options) { | ||
if (options.include) { | ||
this.include(options.include); | ||
} | ||
HotReloader.prototype.onBeforeReload = function(func) { | ||
this.on('beforeReload', func); | ||
return this; | ||
} | ||
if (options.exclude) { | ||
this.exclude(options.exclude); | ||
} | ||
HotReloader.prototype.onAfterReload = function(func) { | ||
this.on('afterReload', func); | ||
return this; | ||
} | ||
/** | ||
* Identify directories/files that will be watched | ||
* for changes. Also, all Node.js modules within these directories | ||
* will automatically be reloaded if a file is changed | ||
* (unless they are explicitly excluded). | ||
*/ | ||
HotReloader.prototype.include = function(includes) { | ||
HotReloader.prototype._shouldUncacheModule = function(moduleName) { | ||
if (!this._uncacheIncludes.length && !this._uncacheExcludes.length) { | ||
return true; | ||
} | ||
if (!Array.isArray(includes)) { | ||
includes = [includes]; | ||
if (hasMatch(this._uncacheExcludes, moduleName)) { | ||
return false; | ||
} | ||
this._includes = this._includes || []; | ||
if (this._uncacheExcludes.length && !this._uncacheIncludes.length) { | ||
return true; | ||
} | ||
for (var i = 0; i < includes.length; i++) { | ||
var dir = resolveFilename(includes[i]); | ||
this._includes.push(dir); | ||
if (hasMatch(this._uncacheIncludes, moduleName)) { | ||
return true; | ||
} | ||
return this; | ||
return false; | ||
} | ||
/** | ||
* Identify directories/files that will not be watched. | ||
* Exclusions take precedence over inclusions. | ||
* | ||
* @param {String[]} list of paths that will not be watched | ||
*/ | ||
HotReloader.prototype.exclude = function(excludes) { | ||
this._excludes = this._excludes || []; | ||
this._excludesMap = this._excludesMap || {}; | ||
HotReloader.prototype._shouldReloadModule = function(moduleName) { | ||
if (!this._reloadIncludes.length && !this._reloadExcludes.length) { | ||
return false; | ||
} | ||
for ( var i = 0; i < excludes.length; i++) { | ||
this._excludes.push(filename = resolveFilename(excludes[i])); | ||
this._excludesMap[filename] = true; | ||
if (hasMatch(this._reloadExcludes, moduleName)) { | ||
return false; | ||
} | ||
return this; | ||
if (this._reloadExcludes.length && !this._reloadIncludes.length) { | ||
return true; | ||
} | ||
if (hasMatch(this._reloadIncludes, moduleName)) { | ||
return true; | ||
} | ||
return false; | ||
} | ||
/** | ||
* Identify all of the modules (and, implicitly, their submodules) | ||
* that will be automatically reloaded if any watched file/directory | ||
* changes. | ||
*/ | ||
HotReloader.prototype.alwaysReload = function(modulePaths) { | ||
this._alwaysReload = this._alwaysReload || []; | ||
for (var i = 0; i < modulePaths.length; i++) { | ||
var modulePath = modulePaths[i]; | ||
HotReloader.prototype.getRequire = function() { | ||
return this._require; | ||
}; | ||
// store the file path of the modulePaths that should always be reloaded | ||
this._alwaysReload.push(resolveFilename(modulePath)); | ||
HotReloader.prototype._reload = function(path) { | ||
console.log('[hot-reload] Beginning reload...'); | ||
var specialReloadHandlers; | ||
if (!hasMatch(this._specialReloadExcludes, path)) { | ||
specialReloadHandlers = getMatches(this._specialReloadIncludes, path); | ||
} | ||
var eventArgs = {path: path}; | ||
var i; | ||
return this; | ||
} | ||
if (specialReloadHandlers.length !== 0) { | ||
this.emit('beforeSpecialReload', eventArgs); | ||
/** | ||
* Identify all of the modules (and, implicitly, their submodules) | ||
* that will never be reloaded. Exclusions given to this method | ||
* call take precedence over includes provided via "alwaysReload". | ||
*/ | ||
HotReloader.prototype.neverReload = function(modulePaths) { | ||
this._neverReload = this._neverReload || []; | ||
for (var i = 0; i < modulePaths.length; i++) { | ||
var modulePath = modulePaths[i]; | ||
for (i=0; i<specialReloadHandlers.length; i++) { | ||
var specialReloadHandler = specialReloadHandlers[i]; | ||
var result = specialReloadHandler(path); | ||
if (result === false) { | ||
break; | ||
} | ||
} | ||
this.emit('afterSpecialReload', eventArgs); | ||
} | ||
else { | ||
this.emit('beforeReload', eventArgs); | ||
// store the file path of the modulePaths that should never be reloaded | ||
this._neverReload.push(resolveFilename(modulePath)); | ||
var modulesToReload = []; | ||
// FIRST PASS: loop through the module cache and remove entries within directories that we are watching | ||
for (var key in require.cache) { | ||
var module = require.cache[key]; | ||
if (this._shouldUncacheModule(module.filename)) { | ||
if (require.cache.hasOwnProperty(key)) { | ||
// delete the cache entry only in first pass | ||
delete require.cache[key]; | ||
console.log('[hot-reload] Uncached module: ' + module.filename); | ||
// keep track of the modules that | ||
if (this._shouldReloadModule(module.filename)) { | ||
modulesToReload.push(module); | ||
} | ||
} | ||
} else { | ||
//console.log('[hot-reload] Not uncaching ' + module.filename); | ||
} | ||
} | ||
for (i = 0; i < modulesToReload.length; i++) { | ||
var module = modulesToReload[i]; | ||
this._reloadModule(module); | ||
} | ||
this.emit('afterReload', eventArgs); | ||
} | ||
return this; | ||
console.log('[hot-reload] Reload complete'); | ||
} | ||
HotReloader.prototype.start = function() { | ||
HotReloader.prototype._reloadModule = function(module) { | ||
console.log('[hot-reload] Reloading module "' + module.filename + '"...'); | ||
var self = this; | ||
this.emit('beforeModuleReload', module); | ||
var walker = directoryWalker.createDirectoryWalker({ | ||
excludes : this._excludes, | ||
delete require.cache[module.filename]; | ||
try { | ||
var newModule = require(module.filename); | ||
onDirectory : function(directory) { | ||
console.log('[Hot Reload] Watching directory: ' + directory); | ||
fs.watch(directory, | ||
function(event, file) { | ||
console.log('[Hot Reload] Changed: ' + file); | ||
self._scheduleReload(); | ||
}); | ||
}, | ||
// copy properties from new module to old module in case their are some | ||
// references to old module | ||
for (var key in newModule) { | ||
if (newModule.hasOwnProperty(key)) { | ||
module.exports[key] = newModule[key]; | ||
} | ||
} | ||
listeners : { | ||
'error' : function(err) { | ||
console.error(err); | ||
console.log('[hot-reload] Reloaded module: ' + module.filename); | ||
} | ||
catch(e) { | ||
console.error('[hot-reload] ERROR: Unable to reload module "' + module.filename + '". Exception: ' + e, e.stack); | ||
} | ||
this.emit('afterModuleReload', module); | ||
} | ||
HotReloader.prototype.start = function(func) { | ||
var watchIncludes = this._watchIncludes, | ||
watchExcludes = this._watchExcludes, | ||
_this = this; | ||
function startWatching() { | ||
function handleModified(event, path) { | ||
var now = Date.now(); | ||
if (_this._lastReloadTime === null || now - _this._lastReloadTime > _this._reloadDelay) { | ||
if (hasMatch(_this._watchExcludeFilters, path)) { | ||
console.log('[hot-reload] Modified file ignored since it is excluded: ' + path + ' '); | ||
// The file excluded from being watched so ignore the event | ||
return; | ||
} | ||
console.log('[hot-reload] File modified: ' + path + ' (' + event + ')'); | ||
_this._reload(path); | ||
_this._lastReloadTime = now; | ||
} | ||
} | ||
}); | ||
if (this._includes) { | ||
for (var i = 0; i < this._includes.length; i++) { | ||
walker.walk(this._includes[i]); | ||
function createWatcherFunc(watchInclude) { | ||
return function(event, filename) { | ||
if (watchInclude.stat.isDirectory()) { | ||
if (!filename) { | ||
handleModified(event, watchInclude.path); | ||
} | ||
else { | ||
handleModified(event, require('path').join(watchInclude.path, filename)); | ||
} | ||
} | ||
else { | ||
handleModified(event, watchInclude.path); | ||
} | ||
}; | ||
} | ||
for (var path in watchIncludes) { | ||
if (watchIncludes.hasOwnProperty(path)) { | ||
if (hasMatch(_this._watchExcludeFilters, path)) { | ||
console.log('[hot-reload] Not watching "' + path + '" since it is excluded.'); | ||
// The path is excluded from being watched...skip it | ||
continue; | ||
} | ||
var watchInclude = watchIncludes[path]; | ||
var watcher = fs.watch( | ||
fs.realpathSync(path), | ||
createWatcherFunc(watchInclude)); | ||
console.log('[hot-reload] Watching ' + (watchInclude.stat.isDirectory() ? 'directory' : 'file') + ': ' + watchInclude.path); | ||
watchInclude.watcher = watcher; | ||
} | ||
} | ||
} | ||
if (this._pending) { | ||
this.on('ready', startWatching); | ||
} | ||
else { | ||
startWatching(); | ||
} | ||
return this; | ||
} | ||
HotReloader.prototype._scheduleReload = function() { | ||
if (this._reloadTimeout) { | ||
clearTimeout(this._reloadTimeout); | ||
exports.create = function(require) { | ||
if (!require) { | ||
throw new Error("require argument is required"); | ||
} | ||
this._reloadTimeout = setTimeout(this.reload, 1500); | ||
return new HotReloader(require); | ||
} | ||
exports.createHotReloader = function(config) { | ||
return new HotReloader(config); | ||
} | ||
exports.HotReloader = HotReloader; | ||
{ | ||
"name": "hot-reload", | ||
"description": "Triggers reloading of Node.js modules", | ||
"version": "1.2.1", | ||
"homepage": "https://github.com/philidem/node-hot-reload", | ||
"authors": [ | ||
{ | ||
"name": "Phil Gates-Idem", | ||
"email": "phillip.gates-idem@gmail.com" | ||
} | ||
], | ||
"main": "./lib/hot-reload/hot-reload", | ||
"dependencies": { | ||
"directory-walker" : "1.2.x" | ||
"name": "hot-reload", | ||
"description": "Triggers reloading of Node.js modules", | ||
"version": "1.2.2", | ||
"homepage": "https://github.com/philidem/node-hot-reload", | ||
"authors": [ | ||
{ | ||
"name": "Phil Gates-Idem", | ||
"email": "phillip.gates-idem@gmail.com" | ||
}, | ||
{ | ||
"name": "Patrick Steele-Idem", | ||
"email": "pnidem@gmail.com" | ||
} | ||
} | ||
], | ||
"main": "./lib/hot-reload/hot-reload", | ||
"dependencies": { | ||
"directory-walker": "1.2.x" | ||
} | ||
} |
node-hot-reload | ||
=============== | ||
Utility code for watching for source file and changes and reloading modules | ||
Utility code for watching for source file and changes and reloading modules | ||
# Installation: | ||
``` | ||
npm install hot-reload | ||
``` | ||
# Usage | ||
Example Usage: | ||
```javascript | ||
require('hot-reload').create(require) | ||
.uncache("*") | ||
.uncacheExclude(__filename) | ||
.specialReload(path.join(__dirname, 'optimizer-config.xml'), initApp) | ||
.specialReload(path.join(__dirname, 'routes.js'), function(path) { | ||
delete require.cache[path]; | ||
initApp(); | ||
}) | ||
.watch(path.join(__dirname, 'modules')) | ||
.watch(path.join(__dirname, 'optimizer-config.xml')) | ||
.watch(path.join(__dirname, 'routes.js')) | ||
.watchExclude("*.css") | ||
.onBeforeReload(function() { | ||
}) | ||
.onAfterReload(function() { | ||
}) | ||
.start(); | ||
``` |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
49329
384
34
5
1