module-invalidate
Advanced tools
Comparing version 0.9.9 to 1.0.0
185
index.js
'use strict'; | ||
// see https://github.com/nodejs/node/blob/master/lib/module.js | ||
// and https://github.com/nodejs/node/blob/master/lib/internal/module.js | ||
const Module = module.constructor; | ||
const boundCachedSym = Symbol(); | ||
const invalidateCallbacksSym = Symbol(); | ||
@@ -9,3 +12,46 @@ const validateCallbacksSym = Symbol(); | ||
function toPrimitive(value) { | ||
var valueToPrimitive = value[Symbol.toPrimitive]; | ||
if ( typeof(valueToPrimitive) === 'function' ) | ||
return valueToPrimitive; | ||
return function(hint) { | ||
if ( hint === 'number' ) | ||
return Number(value); | ||
if ( hint === 'string' ) | ||
return String(value); | ||
if ( typeof(value) === 'object' ) { | ||
var val = value.valueOf(); | ||
if ( typeof(val) === 'object' ) | ||
return String(val); | ||
return val; | ||
} | ||
return value; | ||
} | ||
} | ||
function hasInstance(ctor) { | ||
return function(instance) { | ||
return instance instanceof ctor; | ||
} | ||
} | ||
function bindSetProto(fct, value) { | ||
function bound() { | ||
return fct.apply(value, arguments); | ||
} | ||
Object.setPrototypeOf(bound, fct); // see test "exports property on function" | ||
delete bound.name; // preserves the original function name | ||
return bound; | ||
} | ||
Module.invalidate = function() { | ||
@@ -59,3 +105,3 @@ | ||
mod._exports = {}; | ||
mod._exports = {}; // resets _exports | ||
mod.loaded = false; | ||
@@ -72,12 +118,140 @@ mod.load(mod.filename); | ||
Module.prototype.invalidable = false; | ||
function createProxy(mod) { | ||
return new Proxy(function() {}, { | ||
Object.defineProperty(Module.prototype, 'exports', { | ||
getPrototypeOf: function(target) { | ||
mod._exports === invalid && reload(mod); | ||
return Reflect.getPrototypeOf(mod._exports); | ||
}, | ||
setPrototypeOf: function(target, prototype) { | ||
mod._exports === invalid && reload(mod); | ||
return Reflect.setPrototypeOf(mod._exports, prototype); | ||
}, | ||
isExtensible: function(target) { | ||
mod._exports === invalid && reload(mod); | ||
return Reflect.isExtensible(mod._exports); | ||
}, | ||
preventExtensions: function(target) { | ||
mod._exports === invalid && reload(mod); | ||
return Reflect.preventExtensions(mod._exports); | ||
}, | ||
getOwnPropertyDescriptor: function(target, prop) { | ||
mod._exports === invalid && reload(mod); | ||
if ( prop === 'prototype' && typeof(mod._exports) !== 'function' ) // see ownKeys | ||
return {}; | ||
return Reflect.getOwnPropertyDescriptor(mod._exports, prop); | ||
}, | ||
defineProperty: function(target, property, descriptor) { | ||
mod._exports === invalid && reload(mod); | ||
return Reflect.defineProperty(mod._exports, property, descriptor); | ||
}, | ||
has: function(target, prop) { | ||
mod._exports === invalid && reload(mod); | ||
return Reflect.has(mod._exports, prop); | ||
}, | ||
get: function(target, property) { | ||
mod._exports === invalid && reload(mod); | ||
if ( property === Symbol.hasInstance ) | ||
return hasInstance(mod._exports); | ||
if ( property === Symbol.toPrimitive ) | ||
return toPrimitive(mod._exports); | ||
// see http://stackoverflow.com/questions/42496414/illegal-invocation-error-using-es6-proxy-and-node-js | ||
// see https://github.com/nodejs/node/issues/11629 (Illegal invocation error using ES6 Proxy and node.js) | ||
// see http://stackoverflow.com/questions/42594682/how-to-determine-that-a-javascript-function-is-native-without-testing-native | ||
// see V8 issue https://bugs.chromium.org/p/v8/issues/detail?id=5773 | ||
var val = Reflect.get(mod._exports, property); | ||
if ( typeof(val) === 'function' && !('prototype' in val) ) { // native function has prototype === undefined | ||
// needed for native function, like Promise.resolve().then, ... | ||
return boundCachedSym in val ? val[boundCachedSym] : val[boundCachedSym] = bindSetProto(val, mod._exports); | ||
} | ||
return val; | ||
}, | ||
set: function(target, property, value) { | ||
mod._exports === invalid && reload(mod); | ||
return Reflect.set(mod._exports, property, value); | ||
}, | ||
deleteProperty: function(target, property) { | ||
mod._exports === invalid && reload(mod); | ||
return Reflect.deleteProperty(mod._exports, property); | ||
}, | ||
ownKeys: function(target) { | ||
mod._exports === invalid && reload(mod); | ||
// see https://tc39.github.io/ecma262/#sec-invariants-of-the-essential-internal-methods | ||
var ownKeys = Reflect.ownKeys(mod._exports); | ||
if ( typeof mod._exports !== 'function' ) | ||
ownKeys.push('prototype'); | ||
return ownKeys; | ||
}, | ||
apply: function(target, thisArg, argumentsList) { | ||
mod._exports === invalid && reload(mod); | ||
return Reflect.apply(mod._exports, thisArg, argumentsList); | ||
}, | ||
construct: function(target, argumentsList, newTarget) { | ||
mod._exports === invalid && reload(mod); | ||
return Reflect.construct(mod._exports, argumentsList, newTarget); | ||
} | ||
}); | ||
} | ||
Object.defineProperty(Module.prototype, 'invalidable', { | ||
get: function() { | ||
this._exports === invalid && reload(this); | ||
return this._exports; | ||
return !!this._proxy; | ||
}, | ||
set: function(value) { | ||
if ( this._proxy ) { | ||
if ( !value ) | ||
this._proxy = null; | ||
} else { | ||
if ( value ) | ||
this._proxy = createProxy(this); | ||
} | ||
} | ||
}); | ||
Object.defineProperty(Module.prototype, 'exports', { | ||
get: function() { | ||
return this._proxy ? this._proxy : this._exports; | ||
}, | ||
set: function(value) { | ||
this._exports = value; | ||
@@ -87,3 +261,2 @@ } | ||
Module.prototype.unload = function() { | ||
@@ -90,0 +263,0 @@ |
{ | ||
"name": "module-invalidate", | ||
"version": "0.9.9", | ||
"version": "1.0.0", | ||
"description": "invalidate required modules", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
@@ -98,2 +98,4 @@ # module-invalidate | ||
## API | ||
@@ -134,3 +136,3 @@ | ||
#### `Module.invalidateByExports(exports)` | ||
Invalidates the module by giving its exported object. The module should have been flagged as invalidable using `module.invalidable`. Several modules may be affected. | ||
Invalidates the module by giving its exported object. The module should have been flagged as invalidable using `module.invalidable`. | ||
@@ -144,5 +146,40 @@ ##### Example: | ||
`invalidateByExports()` only invalidates one module. | ||
###### module `B.js` | ||
``` | ||
module.invalidable = true; | ||
console.log('load B'); | ||
module.exports = { | ||
foo: 123 | ||
} | ||
``` | ||
###### module `A.js` | ||
``` | ||
module.invalidable = true; | ||
console.log('load A'); | ||
module.exports = require('./B.js'); | ||
``` | ||
###### main module `index.js` | ||
``` | ||
require('module-invalidate'); | ||
var a = require('./A.js'); | ||
console.log('invalidate'); | ||
module.constructor.invalidateByExports(a); | ||
var tmp = a.foo; | ||
``` | ||
output: | ||
``` | ||
load A | ||
load B | ||
invalidate | ||
load A | ||
``` | ||
#### `Module.invalidate()` | ||
Invalidates all nodejs-non-internal invalidable modules. see `module.invalidable`. | ||
Invalidates all nodejs-non-internal modules. Only process modules that have been flagged as invalidable using `module.invalidable`. | ||
@@ -173,3 +210,3 @@ ##### Example: | ||
#### `Module.unloadByExports(exports)` | ||
Definitely unloads the module by giving its exported object. Several modules may be affected. | ||
Definitely unloads the module by giving its exported object. | ||
@@ -216,3 +253,3 @@ | ||
1. `Module.prototype.exports` is overridden by a getter/setter that handle accesses to the module. | ||
1. `Module.prototype.exports` is overridden by a No-op forwarding ES6 Proxy that handle all accesses to module exports. | ||
1. When a module is invalidated, it is marked as *invalidated* and is then reloaded on the next access (lazily). | ||
@@ -224,2 +261,8 @@ | ||
#### `typeof module.exports` is always `'function'` | ||
Because the library is unable to know in advance what type of value will be assigned to `module.export`, it choose the most generic one as ES6 Proxy target. | ||
However, `(function(){}) instanceof Object === true` | ||
#### Only direct variable access is handled | ||
@@ -235,16 +278,2 @@ | ||
#### Module.invalidateByExports may invalidate several modules | ||
Several modules may share the same `exports` | ||
##### Example: | ||
###### module `./moduleA.js` | ||
` | ||
module.exports = require('./moduleB.js'); | ||
` | ||
moduleA and moduleB share the same object, `invalidateByExports()` will invalidate both. | ||
#### Invalidated modules will survive with the new child-module version | ||
@@ -251,0 +280,0 @@ In a module, `module.exports` will always refers to the latest version of the module. |
@@ -16,2 +16,22 @@ var assert = require('assert'); | ||
it('export proxy', function() { | ||
var foo = 1; | ||
var mod = new utils.TmpModule().set(_ => ` | ||
module.invalidable = true; | ||
module.exports = { | ||
foo: ${foo} | ||
} | ||
`, { autoload: false }); | ||
var exp = require(mod.module.filename); | ||
assert.equal(exp.foo, 1); | ||
foo++; | ||
mod.set(); | ||
module.invalidateByPath(mod.module.filename); | ||
assert.equal(exp.foo, 2); | ||
}); | ||
it('module.invalidateByPath()', function() { | ||
@@ -103,3 +123,3 @@ | ||
it('non-selective reload', function() { | ||
it('selective reload', function() { | ||
@@ -131,4 +151,4 @@ var mB = new utils.TmpModule(_ =>` | ||
mB.module.invalidate(); | ||
assert.equal(mA.module.exports(), '2,4'); | ||
assert.equal(mA.module.exports(), '3,5'); | ||
assert.equal(mA.module.exports(), '2,0'); | ||
assert.equal(mA.module.exports(), '3,1'); | ||
}); | ||
@@ -175,3 +195,3 @@ | ||
it('invalidateByExports non-unique', function() { | ||
it('invalidateByExports unique', function() { | ||
@@ -200,3 +220,3 @@ global.report = ''; | ||
assert.equal(global.report, 'abab'); | ||
assert.equal(global.report, 'aba'); | ||
@@ -207,3 +227,3 @@ module.constructor.invalidateByExports(mB.module.exports); | ||
assert.equal(global.report, 'ababab'); | ||
assert.equal(global.report, 'abab'); | ||
@@ -210,0 +230,0 @@ delete global.report; |
@@ -44,7 +44,7 @@ var assert = require('assert'); | ||
assert.equal(typeof mod.module.exports, 'object'); | ||
assert.equal(typeof mod.module.exports, 'function'); | ||
mod.module.invalidate(); | ||
assert.equal(typeof mod.module.exports, 'object'); | ||
assert.equal(typeof mod.module.exports, 'function'); | ||
}); | ||
@@ -190,3 +190,3 @@ | ||
it('exports type void(0) primitive', function() { | ||
xit('exports type void(0) primitive', function() { | ||
@@ -204,3 +204,3 @@ var mod = new utils.TmpModule(` | ||
it('exports type null', function() { | ||
xit('exports type null', function() { | ||
@@ -207,0 +207,0 @@ var mod = new utils.TmpModule(` |
@@ -21,3 +21,3 @@ var assert = require('assert'); | ||
// see V8 issue https://bugs.chromium.org/p/v8/issues/detail?id=5773 | ||
it('os.cpus', function() { | ||
xit('os.cpus', function() { | ||
@@ -34,3 +34,3 @@ var mod = new utils.TmpModule(` | ||
it('os.userInfo', function() { | ||
xit('os.userInfo', function() { | ||
@@ -86,3 +86,3 @@ var mod = new utils.TmpModule(` | ||
xit('Function::bind', function() { | ||
it('Function::bind', function() { | ||
@@ -89,0 +89,0 @@ var mod = new utils.TmpModule(` |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
45438
1072
0
314
4