ES6 Modules - why named const exports are not read only - javascript

I've been reading about ES modules and experimenting and stumbled upon a case I couldn't explain:
// settings.js
export const FOO = 42;
export const BAR= 5;
// main1.js
import * as settings from './settings';
settings.FOO = 1;
//main2.js
import {FOO, BAR} from './settings'
FOO = 1;
In main1.js I'm able to override the const value through the settings variable, but in main2.js I can't (as expected).
The (theoretical) question is why in the first case it's possible to override the const value? Does creating a "read only view" simply creates properties on a regular object and breaks the original structure?
The practical question would be what's the most effective way to return a collection of constants (or read only properties) from a module? What I had in mind is this:
// settings.js
export default Object.freeze({
FOO: 42,
BAR: 5
});
Any thoughts?
EDIT: I'm using Babel.

The other answer is incorrect.
The (theoretical) question is why in the first case it's possible to override the const value?
This is actually entirely independent of const. With ES6 module syntax, you are not allowed to reassign the exported value of a module, from outside the module. The same would be true with export let FOO; or export var FOO;. Code inside the module is the only thing that is allowed to change exports.
Doing settings.FOO = 1 technically should throw an exception, but most compilers don't handle this particular edge case currently.
As an example, you could do
export var FOO;
export function setFoo(value){
FOO = value;
}
and given this, this is when const becomes useful because it's the same as any other normal JS code. FOO = value would fail if it was declared as export const FOO, so if your module is exporting a bunch of constants, doing export const FOO = 1, FOO2 = 2; is a good way to export constants, it's just that Babel doesn't actually make them immutable.

In this code
import * as settings from './settings';
settings.FOO = 1;
In the above code, you are not assigning directly to the constant variable but a cloned copy in settings.
import * as settings from './settings';
^^^^^^^^^^^^
settings.FOO = 1;
But it is not the case in the next code
import {FOO, BAR} from './settings'
FOO = 1;
Here FOO and BAR are constants and you can't assign to it.

Related

List all exports in ES6 Module syntax

In CommonJS, one can get all exported properties like so:
module.exports.foo = 1234;
module.exports.bar = 5678;
console.log(module.exports); // <-- The variable `exports`/`module.exports` holds an object
// :)
How can I do the equivalent with ES6 module syntax?
export const foo = 1234;
export const bar = 5678;
console.log(module.exports); // <-- Correctly holds the keys/export names, but not their values
// :(
ES modules have
import * as ModuleObj from "./foo";
to import a namespace object containing all of the exports of a module.
For the usecase of module.exports, you can have the module import itself.

What is the difference between with and without curly bracket notation in export/import statements?

I'm new to ES6 and a bit confused with the way classes are exported and imported. It seems many different notations are valid but work differently.
I wrote a class like this in src/web-api.js:
class WebApi {
// ...
}
export { WebApi };
Which I import with:
import { WebApi } from './src/web-api.js'
This works fine, but before I've tried the same thing without curly brackets and it didn't work:
export WebApi; // Tells me '{' expected
import WebApi from './src/web-api.js'; // No syntax error but WebApi is undefined
Even though on the MDN documentation for export, the notation export expression; appears to be valid.
Likewise, this is how React is imported in my application file:
import React, { Component } from 'react';
Why is one class with and another one without curly brackets? In general, how can I tell when to use and not to use curly brackets?
ES6 offers many ways to manage modules through import/export. But there are basically two main strategies:
Default export/import with export default and import module from './module'
Multiple exports/imports with export and import {member} from './module' or import * as module from './module'
(Mixing both is possible but not recommended.)
Module to export/import
function foo() {
console.log('Foo');
}
function bar() {
console.log('Bar');
}
Strategy #1: Default export/import
Export (module.js)
function foo() {
console.log('Foo');
}
function bar() {
console.log('Bar');
}
export default {foo, bar};
/*
{foo, bar} is just an ES6 object literal that could be written like so:
export default {
foo: foo,
bar: bar
};
It is the legacy of the "Revealing Module pattern"...
*/
Import (main.js)
import module from './module';
module.foo(); // Foo
module.bar(); // Bar
Strategy #2: Multiple exports/imports
Export (module.js)
export function foo() {
console.log('Foo');
}
export function bar() {
console.log('Bar');
}
Import (main.js)
import {foo, bar} from './module';
foo(); // Foo
bar(); // Bar
/*
This is valid too:
import * as module from './module';
module.foo(); // Foo
module.bar(); // Bar
*/
As I said previously, ES6 modules are much more complex than that. For further information, I recommend you to read Exploring ES6 by Dr. Axel Rauschmayer, especially this chapter: http://exploringjs.com/es6/ch_modules.html.
In your case, if you import from the src/web-api.js file without the curly braces, you should have anexport default something in the src/webfile-api.js
Without curly braces
class WebApi {...};
export default WebApi;
In your file
import WebApi from './src/web-api.js'
// Here, the element withexport default in the src/web-api.js file should be imported without the curly braces anywhere.
PS: It must have only one export default for each file.
With curly braces
export { WebApi }
In your file
import {WebApi} from './src/web-api.js'
Dan Abramov explains clearly the export/import methods in ES6 at this answer.
When should I use curly braces for ES6 import?
The braces are just syntactic sugar. It will use the variable name as the object key, for example:
const a = 1;
const test = {a}; // same as {a: 1};
It can also be used to destructure the object by its variable name. It will check if the object has any properties with the same value as the variable name and then output a value if one is found:
const test = {a: 1};
const {a} = test; // a = 1
In modules, the general use case is that when you import there is usually braces since modules get imported as MODULE.function or MODULE.class. It'd be more intuitive to look at exports first:
For exporting, it's using the syntactic sugar I mentioned before - you're exporting it as an object. When you do export { WebApi }; what you're really doing is export {WebApi: WebApi}. This makes it easier to access things as you can just do 'MODULE.WebApi' now to access the class instead of having it pollute the namespace unnecessarily. This is also required for all exports!
Moving on to imports, what you're doing in the import statements is essentially destructuring the module object and picking a property to save into a variable of the same name. In your case, when you do import {WebApi} from './src/web-api.js' you'd be doing something like import WebApi = web-api.js['WebApi'] from './src/web-api.js' (this isn't valid syntax but just to show you what it's doing in the background). This is also required to properly import module functions/classes. There is also the option of importing the whole module, just as NodeJS does: import * as ModuleName from './src/module.js'. This will put all of exported functions/classes into the ModuleName object so that it can be treated as a normal module.
However, if a module has a default export, braces are not not needed for import and export. For example, react probably has export default React in its files - that's why there doesn't need to be braces around it when you do import React from 'react'
Hope I wasn't too confusing and let me know if I can clarify anything!

How to use deep destructuring on imports in ES6 syntax?

I noticed that an ES6 destructuring import can be implemented like this:
foo.js
export default () => {
return {
a: 'b'
}
}
index.js
import foo from './foo';
export default foo;
export const bar = foo();
Then I can use the module with:
import foo, { bar } from 'my-module';
But when I use a "deep destructuring" import from my-module, it fails with:
import foo, { bar: { a } } from 'my-module';
It seems like ES6 already implements the above syntax, but how do I use it?
The ImportClause of an import isn't the same as destructuring. They do have some syntactic similarity, but if you read through the spec on import, you can see that it never refers to the usual destructuring constructs such as DestructuringAssignmentTarget or BindingPattern.
Remember that imports create bindings between the modules, but destructuring assignments copy values from a source to a target. With your imagined destructuring import, if the value of bar changes in the source module, would that change your imported a? (After all, with import { bar } from 'my-module';, if bar changes in my-module, the imported bar reflects that change.) Or would the destructuring import copy the value of bar.a to a as of some point in time? And if so, what point in time?
You get the idea. They're just different beasts.
You can, of course import and then destructure:
import foo, { bar } from 'my-module';
let { a } = bar;
...but I'm sure you knew that. :-)

How to import everything exported from a file with ES2015 syntax? Is there a wildcard?

With ES2015 syntax, we have the new import syntax, and I've been trying to figure out how to import everything exported from one file into another, without having it wrapped in an object, ie. available as if they were defined in the same file.
So, essentially, this:
// constants.js
const MYAPP_BAR = 'bar'
const MYAPP_FOO = 'foo'
// reducers.js
import * from './constants'
console.log(MYAPP_FOO)
This does not work, at least according to my Babel/Webpack setup, this syntax is not valid.
Alternatives
This works (but is long and annoying if you need more than a couple of things imported):
// reducers.js
import { MYAPP_BAR, MYAPP_FOO } from './constants'
console.log(MYAPP_FOO)
As does this (but it wraps the consts in an object):
// reducers.js
import * as consts from './constants'
console.log(consts.MYAPP_FOO)
Is there a syntax for the first variant, or do you have to either import each thing by name, or use the wrapper object?
You cannot import all variables by wildcard for the first variant because it causes clashing variables if you have it with the same name in different files.
//a.js
export const MY_VAR = 1;
//b.js
export const MY_VAR = 2;
//index.js
import * from './a.js';
import * from './b.js';
console.log(MY_VAR); // which value should be there?
Because here we can't resolve the actual value of MY_VAR, this kind of import is not possible.
For your case, if you have a lot of values to import, will be better to export them all as object:
// reducers.js
import * as constants from './constants'
console.log(constants.MYAPP_FOO)
Is there a syntax for the first variant,
No.
or do you have to either import each thing by name, or use the wrapper object?
Yes.
well you could import the object, iterate over its properties and then manually generate the constants with eval like this
import constants from './constants.js'
for (const c in constants) {
eval(`const ${c} = ${constants[c]}`)
}
unfortunately this solution doesn't work with intellisense in my IDE since the constants are generated dynamically during execution. But it should work in general.
Sure there are.
Just use codegen.macro
codegen
'const { ' + Object.keys(require('./path/to/file')).join(',') + '} = require("./path/to/file");
But it seems that you can't import variable generated by codegen.
https://github.com/kentcdodds/babel-plugin-codegen/issues/10
In ES6, with below code, imported content can be used without wrap object.
Just put it here for someone like me who ends searching here.
constants.js:
export const A = 2;
export const B = 0.01;
export const C = 0.04;
main.js:
import * as constants from './constants'
const {
A,
B,
C,
} = constants;

es6 - import all named module without alias

I know that we can import all named modules with alias as below,
import * as name from "module-name";
Ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
Actually, I have re-exported my modules in A.js and the same is inherited in B.js. PFB. Now, it's two level of inheritance, so it's not a big deal to import the named modules. But, when I'm taking this to 5 level of inheritance (A -> B -> C -> D -> E), I need to import all named modules in all files and need to do the (re)export the same in all. Instead of doing this,
Is there any other way to copy scope of all named modules into all level without reiterating the wheel (Import and Export)
Behind the scene of this design is to make them to follow Opps concept and to avoid the redeclaration of the same modules.
A.js
import React from 'react';
import I18n from 'i18n-js';
import m1 from 'module1';
import m2 from 'module2';
export default class A extends React.Component {}
export {React, I18n, m1, m2)
B.js
import BaseComponent from './A';
import {React, I18n, m1, m2) from './A;
export default class B extends A {}
Is there any way to import all named modules without alias like import {*} from './A' (instead of 2nd in B.js)
JavaScript solution:
import * as exports from 'foo';
Object.entries(exports).forEach(([name, exported]) => window[name] = exported);
Note: the imported wrapper object remains there.
Node.js solution:
Object.entries(require('foo')).forEach(([name, exported]) => global[name] = exported);
Is there any way to import all named modules without alias like import {*} from './A' (instead of 2nd in B.js)
No.
And the whole idea of re-exporting more than you need to save the "number of lines" in the final js file as you stated at
Because, It's putting two lines for each import in the final js file. Consider If there are 10 import lines than, 20 lines will be added in final js. When you are thinking for production it will too cost
Does not make much sense, since that's what JS minifiers are for.
To summarise: one should not do that at the very first place:
You export only what you need to export
You import whatever you need wherever you need.
You use JS minifiers to optimise the output JS file size.
Here's a crazy experiment I did, that works, but it's probably dangerous in ways I don't fully understand.
Would somebody explain to me why we don't do this?
var lodash = require("lodash");
function $localizeAll(name) {
return `eval("var " + Object.getOwnPropertyNames(${name}).reduce((code, prop)=>{
if (/^[a-zA-Z$_][a-zA-Z$_0-9]*$/.test(prop)) {
return code.concat(\`\${prop} = ${name}["\${prop}"]\n\`);
} else {
console.warn("did not import '" + prop + "'");
return code;
}
}, []).join(", ")+";")`
}
// this will make all exports from lodash available
eval($localizeAll("lodash"));
console.log(add(indexOf([1,2,6,7,12], 6), 5)); // => 7
It's a bit complicated as it evals in two levels, but it basically iterates of all the properties of an object with the given name in scope and binds all properties that have names qualified to be identifiers to an identifier by that name:
var length = lodash["length"]
, name = lodash["name"]
, arguments = lodash["arguments"]
, caller = lodash["caller"]
, prototype = lodash["prototype"]
, templateSettings = lodash["templateSettings"]
, after = lodash["after"]
, ary = lodash["ary"]
, assign = lodash["assign"]
, assignIn = lodash["assignIn"]
, assignInWith = lodash["assignInWith"]
, assignWith = lodash["assignWith"]
, at = lodash["at"]
, before = lodash["before"]
, bind = lodash["bind"]
, bindAll = lodash["bindAll"]
, bindKey = lodash["bindKey"]
//...
, upperCase = lodash["upperCase"]
, upperFirst = lodash["upperFirst"]
, each = lodash["each"]
, eachRight = lodash["eachRight"]
, first = lodash["first"]
, VERSION = lodash["VERSION"]
, _ = lodash["_"]
;
There are some examples in this list of why this is a bad idea (e.g. it shadows arguments).
You should be able to use this as follows (though you probably shouldn't like they say above).
B.js
import BaseComponent, * as extras from './A';
eval($localizeAll("extras"));
export default class B extends BaseComponent {}
Anyways, couldn't resist trying this out ;)
global is your current scope in node.js, similar to the window object in the browser, so you can import into this object.
To import all symbols from util module:
import * as util from "./util";
util.importAll(util, global);
In util.js:
/**
* Takes all functions/objects from |sourceScope|
* and adds them to |targetScope|.
*/
function importAll(sourceScope, targetScope) {
for (let name in sourceScope) {
targetScope[name] = sourceScope[name];
}
}
... and a number of other functions like assert() etc., which I need everywhere, and which should be part of the JavaScript language, but are not yet. But as others said, use this sparingly.
For Now, there is no clean way to do this.
But you can overcome the problem by :
1) defining an alias
import * as foo from "foo"
2) writing all modules
import {a,b,c,d,...} from "foo"

Categories

Resources