I am trying to deprecate a module with many named exports like so:
const Foo = () => 'foo';
const Bar = () => 'bar';
export { Foo };
export { Bar };
which is fine when consuming via:
import { Foo, Bar } from './something';
My thoughts about enabling deprecation warnings was to use a default export of type object with a property getter override for each key that prints the deprecation and then returns the module.
The shape then becomes something like:
const something = {};
Object.defineProperty(something, 'Foo', {
get(){
console.warn('Foo is deprecated soon');
return Foo;
}
});
// etc
export default something;
My thinking had been that the destructuring import would figure it out so
import { Foo, Bar } from './something';
... would continue to work as before. Instead, webpack complains that something does not have a named export Foo or Bar
using this, however, works:
const { Foo, Bar } = require('./something');
I can also have import something from './something'; const { Foo, Bar } = something and that works but it defeats the purpose if I have to refactor every import that exists.
So the question really is about how import { Foo, Bar } from './something'; works compared to the require() - I'd have thought that if the default export is an object, it would figure it out and destructure, but no joy.
Is there an easy way to do this 'proxying' without changing how the exports are being consumed elsewhere?
I think i made it work. Bare in mind that this is a workaround.
Given that you said that the library is being "re-exported" from a single file you could add an additional "layer" to the "re-export" where we turn the "re-exportation" file into a JS file and write our own associated typing file for it.
Working repl: https://repl.it/#Olian04/CelebratedKlutzyQuotes
Code snippets:
// index.ts
// consuming the library
import { Foo } from './demo';
console.log(Foo());
// library.ts
// the library it self
const Foo = () => 'foo';
export { Foo }
// demo.js
// the workaround implementation
const depricatedLibrary = require('./library');
const something = new Proxy(depricatedLibrary, {
get(obj, key) {
if (typeof key === 'string') {
console.warn(key + ' is deprecated soon');
}
return obj[key];
}
});
module.exports = something;
// demo.d.ts
// the workaround types
export * from './library';
Related
I'm wondering how the exported bindings of an ES6 module work when destructuring. I'm exporting an object (thing) as a named export -- but also one of its properties (foo), too, which I'm later updating.
This update works when I import the object and reference its prop, but not when I directly import foo, as shown below. I'm guessing the export const... creates a new, immutable binding, but is there any way to retain its reference to the original object upon export?
// module: 'thing.js'
let thing = {
foo: (x) => console.log(x)
};
const init = () => {
thing.foo = (x) => console.log("updated");
};
export { init, thing };
export const { foo } = thing;
// index.js
import { foo, thing, init } from "./thing";
init();
foo("test"); // does not work
thing.foo("test"); // update is reflected, here
codesandbox
There is not issue with export/import
see this example:
let thing = {
foo: (x) => console.log(x)
};
const init = () => {
thing.foo = (x) => console.log("updated");
};
const foo = thing.foo;
init();
foo("test"); //test
thing.foo("test"); //updated
Variable foo and field thing.foo contains different functions after you rewrite thing.foo inside init function
The odds are that this question has been asked many times, but still can't find the answer.
Imagine I have two modules A and B, and function foo defined in module A. In module B, I have a function foo which needs foo from A:
module B:
import { foo } from './A'
export const foo = (payload) => {
foo() // meaning module A function
}
I get error
Identifier 'foo' has already been declared
Any way to solve this leaving the function names the same ?
No, you cannot leave the identifiers the same, you need to alias either the imported one
import { foo as aFoo } from './A'
export const foo = (payload) => {
aFoo()
};
import * as A from './A'
export const foo = (payload) => {
A.foo()
};
or change the local function name:
import { foo } from './A'
const bBoo = (payload) => {
foo()
};
export { bFoo as foo }
I have just changed my lodash import from import _ from 'lodash'; to import debounce from 'lodash/debounce';
In my test I used to have sandbox.stub(_, 'debounce').returnsArg(0);, but now I'm stuck as to what to change it to. Obviously sandbox.stub(debounce).returnsArg(0); won't work. Not sure what to do when only a single function is exported from a module.
This syntax:
import something from 'myModule';
...is ES6 syntax which binds something to the default export of 'myModule'.
If the module is an ES6 module then you can stub the default export of the module like this:
import * as myModule from 'myModule';
const sinon = require('sinon');
// ...
const stub = sinon.stub(myModule, 'default');
...but this only works if 'myModule' is an ES6 module.
In this case 'lodash/debounce' is not an ES6 module, it is shipped pre-compiled. The last line is this:
module.exports = debounce;
...which means the module export is the debounce function.
This means that in order to stub 'lodash/debounce' you have to mock the entire module.
Sinon doesn't provide module-level mocking so you will need to use something like proxyquire:
const proxyquire = require('proxyquire');
const sinon = require('sinon');
const debounceStub = sinon.stub().returnsArg(0);
const code = proxyquire('[path to your code]', { 'lodash/debounce': debounceStub })
...or if you are using Jest you can use something like jest.mock:
jest.mock('lodash/debounce', () =>
jest.fn((func, ms) => func) // <= mock debounce to simply return the function
);
Details
The reason that stubbing the default export of a module only works if it is an ES6 module is because of what happens during compilation.
ES6 syntax gets compiled into pre-ES6 JavaScript. For example, Babel turns this:
import something from 'myModule';
...into this:
var _myModule = _interopRequireDefault(require("myModule"));
function _interopRequireDefault(obj) {
return obj && obj.__esModule ?
obj : // <= return the result of require("myModule") if it is an ES6 module...
{ default: obj }; // <= otherwise set it to the default property of a wrapper object
}
...so if 'myModule' is an ES6 module it gets returned directly...but if it is not then the interop returns a wrapping object.
Since each import gets a different wrapping object, changing the default property on one doesn't affect the default property of any others.
you can make yourself a wrapping file, which will eventually export the same lodash/debounce instance for you, but this time you could stub that, e.g.:
myutils/lodash/debounce.js
import lodashDebounce from 'lodash/debounce';
const exports = {
debounce: lodashDebounce,
};
export const debounce = () => exports.debounce();
export default exports;
now, in your actual code import the debounce not from the original location, but from this wrapping file instead:
BEFORE:
import debounce from 'lodash/debounce' // this is how we usually do
AFTER:
import { debounce } from 'myutils/lodash/debounce' // if we want to stub it
// all other code-lines remain the same
const method = () => {
debounce(callback, 150));
...
}
now when doing a test.js:
import lodashWrapped from 'myutils/lodash/debounce';
sinon.stub(lodashWrapped , 'debounce').callsFake((callbackFn) => {
// this is stubbed now
});
// go on, make your tests now
Two instances of code:
const obj = {}
export default obj
export default obj = {}
Flow gives an error on the second
Cannot resolve name obj
Is there something wrong with the code in second place? It works just fine, but flow warns me.
const obj = {
// property goes here
}
export default obj
or,
export default {
// property goes here
}
or
let obj;
export default obj = {
// property goes here
}
Ref: https://developer.mozilla.org/en-US/docs/web/javascript/reference/statements/export
My react native project skeleton here
-app
--component
--LoginScreen.js
--container
--styles.js
-index.ios.js
-index.android.js
and styles.js....
...
export const colors = {
'green' : '#######'
....
}
export const test = () => {
console.log(arguments);
}
...
and LoginScreen.js
import { test } from '../styles';
export default class LoginScreen {
....
constructor () {
test();
}
....
}
so watch chrome debug console...
Arguments[5]
0:DedicatedWorkerGlobalScope
1:_require(moduleId)
2:Object
3:Object
4:null
callee:(global, require, module, exports)
length:5
Symbol(Symbol.iterator):values()
__proto__:Object
what is this?
imported function always return arguments[5]
I don't know why return that arguments.
I think that this related import? function.
Let me know please
Arrow functions do not bind their arguments. If you want to use variable number of arguments in React Native, you can use rest parameter syntax ... to get an array of arguments.
export const test = (...args) => {
console.log(args);
}
The arguments object should work with named function expressions:
export function test() {
console.log(arguments);
}