Avoiding the "default" property indirection with babel - javascript

I am using babel to transpile ES2015 code to ES5 & RequireJS.
But when I use the following syntax:
const o = { foo: 'foo' };
export default o;
The transpiled result is an object with a default property on it.
ie. it is currently transpiled to something like:
define(function() {
return {
default: {
foo: 'foo'
}
};
});
What I want is the object literal itself (containing the foo property) to be returned directly.
ie. I want something like:
define(function() {
return {
foo: 'foo'
};
});
Can I achieve this?

If you don't include default it will work as expected.
export const o = { foo: 'foo' };

Related

Stop VS Code from adding 'as' or alias in destructured assignment when renaming in JavaScript or TypeScript

When I rename a variable in JavaScript or TypeScript, VS Code sometimes adds aliases in destructured assignments:
const { renamedProp: prop } = arg; // After rename
or it adds as in imports:
import { Foo as renamedFoo } from "./file"; // After rename
Why does VS Code do this and how can I disable this behavior? For example, if I rename prop in interface Foo for the following code:
export interface Foo {
prop: string;
}
function bar(arg: Foo) {
const { prop } = arg;
return prop;
}
VS Code changes the code to:
export interface Foo {
renamedProp: string;
}
function bar(arg: Foo) {
const { renamedProp: prop } = arg;
return prop;
}
I want it to be:
export interface Foo {
renamedProp: string;
}
function bar(arg: Foo) {
const { renamedProp } = arg;
return renamedProp;
}
By default, VS Code attempts to make renames safe. This means preserving the existing interfaces of types. In cases like the following example,
export interface Foo {
prop: string;
}
function bar(arg: Foo) {
const { prop } = arg;
return { prop };
}
If we rename prop without using aliases, the implicitly returned type of bar would change. And maybe this type was used to satisfy another interface that expects a property called prop. In this case, introducing an alias on rename preserves the existing type interfaces whichs ensures that the code continues to compile and work as expected
To disabled this behavior, just set:
"javascript.preferences.useAliasesForRenames": false,
"typescript.preferences.useAliasesForRenames": false,
These settings are only supported when using TypeScript 3.4+ in your workspace (this is the default in VS Code 1.33+)

Destructure a function parameter subproperty

I've got a Function that I want to be able to call in 2 ways - and it should behave the same.
Is there any ES6 syntax that will allow me to call the function doBar below using both ways with the same result?
Consider a function like this:
const doBar = ({ foo = 'bar' }) => {
console.log(foo) // should log 'baz'
}
I'm using a framework that binds events like so:
<x-component on-some-event="doBar"></x-component>
which will essentially cause an invocation like so:
// where e = { detail: { foo: 'baz' } }
doBar(e)
.. but I'd like to be able to both call my Function explicitly as well, albeit with a proper call signature like so:
doBar({ foo: 'baz' })
You can use a default parameter. If foo is not found, it will use the value of detail.foo.
const doBar = ({ detail = {}, foo = detail.foo }) => {
console.log(foo) // should log 'baz'
}
doBar({ foo: 'baz' })
doBar({
detail: {
foo: 'baz'
}
});
You can't do this properly in the parameter declaration. Just put your destructuring assignment in the function body:
function doBar(e) {
const { foo = "bar", qux } = e.detail || e;
consoel.log(foo, qux);
}
I'd like to be able to both call my function explicitly as well
I would recommend to not do that. A function should have one signature only and not be overloaded to different types of arguments. Just be explicit about when to use what:
function doBar({ foo = "bar", qux }) {
console.log(foo);
}
function withDetail(fn) {
return e => fn(e.detail)
}
<x-component on-some-event="withDetail(doBar)"></x-component>
doBar({ foo: 'baz' })
No. Not unless you consider this to be an adequate alternative:
const thing = {
detail: {
foo: 'baz'
}
};
doBar(thing.detail);

Named default ES6 parameter without destructuration

I'm trying to default options in ES7 using babel. Here is what I can do:
class Foo {
constructor({key='value', foo='bar', answer=42}) {
this.key = key;
this.foo = foo;
this.number = number;
}
}
This might work for this example, but I would like to know how can I assign for very large config objects; here is an example of what I wanna do:
class Foo {
constructor(opts = {key='value', foo='bar', answer=42}) {
this.opts = opts;
}
}
However this does not compile. I tried to do it like this:
class Foo {
constructor(opts = {key:'value', foo:'bar', answer:42}) {
this.opts = opts;
}
}
But then it replaces the whole object, like this:
let foo = new Foo({key: 'foobar'});
console.log(foo.opts);
// {key: 'foobar'} is what is displayed
// When I want {key: 'foobar', foo:'bar', answer:42}
I don't think you can do this with ES6 optional parameters (object as a parameter with optional keys), because when you call the constructor, it's a new object with a new reference. That's because it's being replaced.
But, as a suggestion, if you want to handle a large options object, one common approach is store somewhere a default options Object and merge the object with the one passed when you instantiate it.
Something like that:
class Foo {
constructor(opts) {
this.opts = Object.assign({}, Foo.defaultOptions, opts)
console.log(this.opts)
}
}
Foo.defaultOptions = {
key: 'value',
foo: 'bar',
answer: 42
}
let foo = new Foo({key: 'another value'})
//{ key: 'another value', foo: 'bar', answer: 42 }
You can merge with Object.assign (be aware that it does not perform deep merging - nested objects are replaced).
Or, if you want to declare your default options Object as a class variable (not at the end, after class declaration, or inside constructor), as you're using babel, you can use this plugin and do this:
class Foo {
defaultOptions = {
key: 'value',
foo: 'bar',
answer: 42
}
constructor(opts) {
this.opts = Object.assign({}, this.defaultOptions, opts)
console.log(this.opts)
}
}
It's more readable.
It is
class Foo {
constructor({key='value', foo='bar', answer=42} = {}) {
...
}
}
It is ES6 destructuring feature, not specific to ECMAScript 7 (ECMAScript Next) proposals.
Without destructuring it is usually done with object cloning/merging, Object.assign comes to help:
class Foo {
constructor(opts = {}) {
this.opts = Object.assign({
key: 'value',
foo: 'bar',
answer: 42
}, opts);
}
}

Generic reading of arguments from multiple constructor calls

Follow-up question to Read arguments from constructor call:
The accepted solution allows me to get arguments passed into a constructor by defining a wrapper class that captures and exposes the arguments, but this leaves me with the problem of having n wrappers for n constructors.
Is there a way to have 1 function/wrapper/whatever that could work for any number of constructors?
I'll reiterate that I'm pursing this technique specifically to test Webpack plugin configuration, and I'd like to avoid having a separate wrapper for each plugin that I need to test.
Looking for something along the lines of
// ------------------------------------------------------------ a wrapper function?
const someWrapper = () => { /* ... */ }
const plugin1 = new Plugin({ a: 'value' })
const plugin2 = new Plugin2(arg1, arg2, { b: 'anotherValue '})
someWrapper(plugin1).args === [{ a: 'value' }]
someWrapper(plugin2).args === [arg1, arg2, { b: 'anotherValue' }]
// --------------------------------------------------------------- a wrapper class?
class Wrapper { /* ... */ }
const plugin1 = new Wrapper(Plugin, [{ a: 'value' }])
const plugin2 = new Wrapper(Plugin2, [arg1, arg2, { b: 'anotherValue '}])
plugin1.args === [{ a: 'value' }]
plugin2.args === [arg1, arg2, { b: 'anotherValue '}]
// problem with above is the wrapper is being passed to Webpack, not the underlying
// plugin; not sure yet if this would cause webpack to break or not actually
// execute the plugin as intended with a vanilla config
// ---------------------------------------------------------------- something else?
Yes, you can create generic wrapper which will add args property to instance of any passed constructor:
class Plugin {
constructor (arg1, arg2) {
this.arg1 = arg1
this.arg2 = arg2
}
}
function wrapper(initial) {
// Rewrite initial constructor with our function
return function decoratedContructor(...args) {
// Create instance of initial object
const decorated = new initial(...args)
// Add some additional properties, methods
decorated.args = [...args]
// Return instantiated and modified object
return decorated
}
}
const decoratedPlugin = wrapper(Plugin)
const plugin = new decoratedPlugin('argument', { 'argument2': 1 })
console.log(plugin.args)
FYI: it's not safe to add properties without some prefix. Consider adding __ or something like this to your property, because you can accidentally rewrite some inner object property.
I was able to get this working with a modification to #guest271314's suggestion, namely, you need to pass ...initArgs to super(), otherwise webpack will fail with a TypeError: Cannot read property '...' of undefined.
Also took #terales's point into account about making sure to prefix my additional properties.
const exposeConstructorArgs = (Plugin, ...args) => {
const ExposedPlugin = class extends Plugin {
constructor(...initArgs) {
super(...initArgs);
this.__initArgs__ = initArgs;
}
get __initArgs() {
return this.__initArgs__;
}
};
return Reflect.construct(ExposedPlugin, args);
};
// ...
const dllPlugin = exposeConstructorArgs(webpack.DllPlugin, {
name: '[name]',
path: path.join(buildDir, '[name].json'),
});
// ...
const pluginConfig = dllPlugin.__initArgs[0];
expect(pluginConfig.name).toEqual('[name]');
You can use a generic function where class expression is used within function body. Pass reference to the class or constructor and parameters expected to be arguments within the instance to the function call.
function Plugin() {}
function Plugin2() {}
function PluginWrapper(pluginRef, ...args) {
let MyPlugin = class extends pluginRef {
constructor() {
super();
this.args = [...arguments];
}
getArgs() {
return this.args;
}
}
return Reflect.construct(MyPlugin, args);
};
const anInstance = PluginWrapper(Plugin, {
a: 'path'
});
console.log(anInstance.getArgs(), anInstance instanceof Plugin);
const aSecondInstance = PluginWrapper(Plugin2, "arg1", "arg2", {
b: 'anotherPath'
});
console.log(aSecondInstance.getArgs(), aSecondInstance instanceof Plugin2);

Possible strange behaviour with ES6 deconstruction syntax [duplicate]

This question already has answers here:
Babel 6 changes how it exports default
(4 answers)
Closed 6 years ago.
I've been using the deconstruction syntax { ...variable } for quite awhile now, but I've never really had a problem with it until today, while most of my use cases still work as expected this one is a bit confusing to myself.
I have a JS file that generates an object and exports it, ex:
var exports = {}
...
export default exports;
There are not any nested objects and by the end of the file it's a simple KVP.
When trying to import from this file any objects I attempt to get through deconstruction are undefined. For example:
import { Foo, Bar } from './my-object';
Foo.bar(); // Cannot read property bar of undefined
However, if I break it apart farther like so, everything is fine:
import MyObject from './my-object';
const { Foo, Bar } = MyObject;
Foo.bar(); // Works!
I've tried changing exports to a different variable name as I thought maybe, just maybe it was a confliction with module.exports, but that wasn't the problem.
In the past whenever I exported an object it was simple:
export default { ... }
I'm really confused on what the issue could be this time around as the result of console.log(exports) is the same thing:
{ Foo: foo, Bar: bar }
Where bar() is a function variable of foo
I should also add that trying to hack this to have the proper results doesn't work either, for example:
export default {
Foo: { bar: () => {} },
Bar: { foo: () => {} }
};
Still throws the same Cannot read property __ of undefined
If you are using Babel, it's because the export default statement is roughly translated to:
var foo = {};
exports["default"] = foo;
And import MyObject from './my-object' is translated to: var MyObject = require('./my-object').default;. Which is why your second example works.
However, when you're doing import { Foo, Bar } from './my-object', it is translated to var { Foo, Bar } = require('./my-object');
See this question for extra details.
In your case, I recommend just using the normal export statement. For example:
export class Foo {
myMethod() {}
};
export const Bar = { a: '1' };
Then you can do import { Foo, Bar } from './my-object';.
I think You just miss the basic concept.
In this case You are exporting a single object not multiple classes that is why your import is not working as you are thinking.
The syntax works when we have a file say sample.js with
export var A = (function () {
function A() {
}
return A;
}());
export var B = (function () {
function B() {
}
return B;
}());
export var C = (function () {
function C() {
}
return C;
}());
And if you are importing in other file with the following manner :
import { A, B, C } from './sample';
I have not tried this but I think It is working like this.
if you want to import { Foo, Bar} from './my-object', you need to explicitly export the Foo and Bar. e.g.
export const Foo = { bar() {} };
export const Bar = { foo() {} };

Categories

Resources