JavaScript module import wildcard * versus explicit import - javascript

QUESTION
There are a number of posts about this topic, but I'm still unsure as to the best practice for JS imports.
STYLE GUIDES CONFLICT
Google Style Guide Google Import Style Guide suggest the following approach is "good"
import * as goog from '../closure/goog/goog.js';
AirBNB Style Guide AirBNB Style Guide clearly states not use wildcards.
Observation
As I look through some respected JS developers I see them use wildcards. Originally I suspected it was for namespace or aliasing purposes, but I've seen this type of example more often than not.
import * as debounce from 'lodash/debounce.js'
The only reason I can imagine is maintainability i.e. debounce will remain debounce independent of export, and in this cases debounce will debounce as long as a debounce.js file exists.
The difference between
OPTION 1:
import debounce from "lodash/debounce.js";
OR
OPTION 2:
import * as debounce from 'lodash/debounce.js'
Is that option 1 seems to generate (example only)
var _debounce = require('./lodash/debounce.js');
and option 2 adds a bit of iteration code.
var _debounce = require('./lodash/debounce.js');
var Debounce = _interopRequireWildcard(_debounce);
function _interopRequireWildcard(obj) {
if (obj && obj.__esModule) {
return obj;
} else {
var newObj = {};
if (obj != null) {
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key))
newObj[key] = obj[key];
}
}
newObj.default = obj;
return newObj;
}
}
Best Practice?
Is there a best practice for the average JS developer?
import * as debounce from 'lodash/debounce.js' // wildcard import
import debounce from 'lodash/debounce.js' // submodule import
import {debounce} from 'lodash' // deconstruction import
import _ , { debounce, throttle } from 'lodash'; // everything but the sink
import debounce from 'lodash.debounce' // method import
Method vs Submodule
As a side note there is also conflict about submodule vs. method imports i.e.
Submodule Import
import debounce from 'lodash/debounce'
Method Import
import debounce from 'lodash.debounce'
but I read that method imports don't share code and as such the memory footprint will be slightly larger, whereas submodules do, which would be important in larger libraries.
My gut feel is wildcard imports are a code smell (like infinity in science), but I feel I might misunderstand the nuances in the style guides.
Is the takeaway here that both are right, but only use wildcards
for maintainability / longevity when importing 3rd party code
smaller libraries or utilities a more efficient approach
namespacing?
or ...

...but I've seen this type of example more often than not.
import * as debounce from 'lodash/debounce.js'
The only reason I can imagine is maintainability i.e. debounce will remain debounce independent of export, and in this cases debounce will debounce as long as a debounce.js file exists.
While that's true, it's not particularly useful, because debounce in that example is just an object (specifically, it's called a module namespace object). It's just a container. It has properties for all the named exports in debounce.js and if there's also a default export, a property called default. Those (other than default of course) will still change if the exports in debounce.js change.
The difference between
OPTION 1:
import debounce from "lodash/debounce.js";
OR
OPTION 2:
import * as debounce from 'lodash/debounce.js'
The difference is in what they do, not (just) the output that (say) Webpack creates for them. They do different things.
import debounce from 'lodash/debounce.js' imports the default export from debounce.js and binds it to a local binding (loosely, variable) called debounce.
import * as debounce from 'lodash/debounce.js' imports ALL of the exports from debounce.js wrapped up in a module namespace object and binds that object to a local binding called debounce.
In the first example, assuming the default export of lodash/debounce.js is a function, you'd use it as:
const newFunction = debounce(someFunction, someDelay);
In the second example, you'd have to use a property on the module namespace object; assuming it's the default export, that would be:
const newFunction = debounce.default(someFunction, someDelay);
Those are fundamentally different.
So the best practice is to do the thing you actually want to do. Sometimes, you want the module namespace object, though (opinion!) I suspect that's fairly rare. Generally, you probably want to import specific exports, either the default one (import x from ...) or a named one (import { x } from ...).

Related

Is there a use case for exporting the same const as a named and default export?

Question
I have seen a large codebase where every file with constants looks something like this:
export const DEFAULT_ID = 0;
export const CURRENT_CODE = 'ABC123';
export default {
DEFAULT_ID,
CURRENT_CODE
};
They are using both a named as well as a default export for all constants. When it comes to how the constants are actually being imported, it seems that they are usually simply importing them as named exports:
import {CURRENT_CODE} from './whatever.consts';
Is there any use case for this practice? It’s unusual, since, normally, either a named or a default export is used, not both.
Research
I checked this question hoping to get some insight into why one would use them together in this manner, but I couldn’t find anything.
The closest thing I got to an answer was in this article. In the section “Why expose a symbol as both default and named exports?”, they provide a brief example of how this allows someone to use import {A, B} from './a' instead of needing to write something like import A, {B} from './a'. However, this explanation doesn’t make sense to me since the same syntax can be used if the constants are simply exported as named exports.
My Thoughts
The only reason I can think of is that this approach can give more flexibility when it comes to importing constants. I.e., it allows using both
import {DEFAULT_ID, CURRENT_CODE} from './whatever.consts';
let id = DEFAULT_ID, code = CURRENT_CODE;
and
import INITIALIZATION_CONSTS from './whatever.consts';
let id = INITIALIZATION_CONSTS.DEFAULT_ID, code = INITIALIZATION_CONSTS.CURRENT_CODE
for importing the constants.
Is this a valid reason for using this approach? Are there any best practices implications?
Is there any use case for this practice?
Not really. The only thing I can think of is backwards-compatibility, possibly related to how they are transpiling their code, if the module is a library used elsewhere.
The only reason I can think of is that this approach can give more flexibility when it comes to importing constants.
A default export is not necessary for that. You can easily use a namespace import with named exports only:
import * as INITIALIZATION_CONSTS from './whatever.consts';
let id = INITIALIZATION_CONSTS.DEFAULT_ID, code = INITIALIZATION_CONSTS.CURRENT_CODE

Import { map } from 'lodash', 'rxjs', 'ramda' at the same time without hurting readability

How does one import map or merge or any other function from multiple imports?
import { map } from 'lodash';
import { map } from 'rxjs/operators';
import { map } from 'ramda';
The obvious answer is to:
import { map as _map } from 'lodash';
import { map as rxMap } from 'rxjs/operators';
import { map as RMap } from 'ramda';
But this is ugly and obscures code. I consider this a hack, not a solution, but a workaround due to the limitations of static analysis
I can think of another way:
import * as _ from 'lodash';
import { map } from 'rxjs/operators';
import * as R from 'ramda';
However, the JS community frowns upon this due to tree-shaking reasons. However, I am in the belief that it is over-exaggerated, only saving 45kb.
Basically you can create your own util bundles. For example:
// utils/lodash.js
export { map, get, set } from 'lodash';
// yourScript.js
import * as _ from 'utils/lodash';
It is honestly up to what you / whoever you're working for values more. Sometimes the extra 45kb is terrible but most of the time, and especially on personal project nobody should care. And if it is going to make your programming more efficient go with whatever works best for you.
If you're using those map features for specific use cases (observables, array/object manipulation, etc.) throughout your application, it is probably best to rename the methods to reflect your application's specific use case.
I'd treat them the same as if you had three non-vendor methods called "map". If you found this in your own code, you would determine if all the methods do the same thing, and if not, rename them to be more specific about what they do.
For example, rxjs.map would become mapObservable, etc. There would be a maintenance hit enforcing your new name across your modules, but the benefit would be that there is less context switching your developers will need to do to understand what's being used and why.
This may be a heavily subjective question, as the answer is likely going to vary by team, conventions, and your application.
P.S. One ways of reducing the maintenance would be to expose these methods through a wrapper, and have your team use this wrapper for those specific functions.

Why capitalize React here? import * as React from 'react'

Most examples I see of importing modules use lowercase; e.g.
import * as tools from './tools';
The rule I usually see is, if it's a constructor function then use PascalCase; otherwise use camelCase.
However I always see import * as React from 'react' even though React is not a constructor function (can't do new React()). Why is it always capitalized, and when from a JavaScript style standpoint would I choose to capitalize a library or module like './tools'?
Most of my background is in C# and C++ so I'm inclined to capitalize libraries (import DateFns from 'date-fns', import * as Tools from './Tools').
Because it is a namespace and I guess because it is a framework brand. Brands that are frameworks tend to get capitalized more often (Vue, Backbone, Ember, etc).
When using Typescript as your javascript super-set it makes a whole lot of sense to capitalize your namespaces, since it has a C# like flavor to it.
But even with new ECMAScript versions there is a benefit to capitalize namespaces. I think it keeps things more readable.
I just stumbled on this older question.
The primary reason why the 'react' module needs to be imported as React (exact spelling and capitalization) is so that JSX works properly.
From the JSX In Depth doc:
Fundamentally, JSX just provides syntactic sugar for the React.createElement(component, props, ...children) function.
...and later:
React Must Be in Scope
Since JSX compiles into calls to React.createElement, the React library must also always be in scope from your JSX code.
Example
This component in JSX:
const SimpleComponent = () => (<div>simple component</div>);
...gets compiled into this:
var SimpleComponent = function SimpleComponent() {
return React.createElement("div", null, "simple component");
};
...so React must exist in the scope or the compiled code will throw this error when it runs:
ReferenceError: React is not defined

Simplifying the `import as` syntax

I'm currently importing a few lodash functions with the following syntax:
import {cloneDeep as _cloneDeep, each as _each, size as _size} from 'lodash'
This works, but it's kind of annoying to have to specify each as variable renaming. Are there any shortcuts that would avoid this? I'd be happy to have all those methods inside an object as well, to simulate the functionality of when the entire lodash library is imported, e.g. _.cloneDeep, _.each, etc.
To clarify, I don't want to import the entire library. So I'm looking for a solution that still allows the modular importing of functions.
Since your goal is to avoid annoyance I suspect this may not be what you want but if you're very set on the consumer syntax and want individual importing for efficient packing or something you could always create an intermediary module.
This module will import the contents that you want and in your main code you can reference the intermediary module in the way you're hoping to.
As an example:
import {parse as _parse, join as _join} from 'path'
export var parse = _parse;
export var join = _join;
and in your main application,
import * as path from './custom-lib/path'
path.parse(...);
It involves an extra file which sucks, but you do only have to write it once.
I wouldn't necessarily call this a good solution, but I think this is the closest you're going to get to what you want with the current standards.
ECMA2015 has the option to import all into one object/container.
import * as _ from 'lodash'
then you can use it as before:
_.cloneDeep(), _.each(), etc...
You can import the whole lib as well as individual methods:
import _, { cloneDeep, each, isArray } from 'lodash';
_.isArray([]) // true
isArray([]) // true
When you do import x from 'y'; it will store the whole y module in the x variable.
Doing import {a, b, c} from 'y'; will just pull in the specific methods a, b, c of the module.
You can combine both types of import by just delimiting them with a comma as I did in the example.
Learn more at MDN's reference.

Correct way to import lodash

I had a pull request feedback below, just wondering which way is the correct way to import lodash?
You'd better do import has from 'lodash/has'.. For the earlier version
of lodash (v3) which by itself is pretty heavy, we should only import
a specidic module/function rather than importing the whole lodash
library. Not sure about the newer version (v4).
import has from 'lodash/has';
vs
import { has } from 'lodash';
Thanks
import has from 'lodash/has'; is better because lodash holds all it's functions in a single file, so rather than import the whole 'lodash' library at 100k, it's better to just import lodash's has function which is maybe 2k.
If you are using webpack 4, the following code is tree shakable.
import { has } from 'lodash-es';
The points to note;
CommonJS modules are not tree shakable so you should definitely use lodash-es, which is the Lodash library exported as ES Modules, rather than lodash (CommonJS).
lodash-es's package.json contains "sideEffects": false, which notifies webpack 4 that all the files inside the package are side effect free (see https://webpack.js.org/guides/tree-shaking/#mark-the-file-as-side-effect-free).
This information is crucial for tree shaking since module bundlers do not tree shake files which possibly contain side effects even if their exported members are not used in anywhere.
Edit
As of version 1.9.0, Parcel also supports "sideEffects": false, threrefore import { has } from 'lodash-es'; is also tree shakable with Parcel.
It also supports tree shaking CommonJS modules, though it is likely tree shaking of ES Modules is more efficient than CommonJS according to my experiment.
Import specific methods inside of curly brackets
import { map, tail, times, uniq } from 'lodash';
Pros:
Only one import line(for a decent amount of functions)
More readable usage: map() instead of _.map() later in the javascript code.
Cons:
Every time we want to use a new function or stop using another - it needs to be maintained and managed
Copied from:The Correct Way to Import Lodash Libraries - A Benchmark article written by Alexander Chertkov.
You can import them as
import {concat, filter, orderBy} from 'lodash';
or as
import concat from 'lodash/concat';
import orderBy from 'lodash/orderBy';
import filter from 'lodash/filter';
the second one is much optimized than the first because it only loads the needed modules
then use like this
pendingArray: concat(
orderBy(
filter(payload, obj => obj.flag),
['flag'],
['desc'],
),
filter(payload, obj => !obj.flag),
If you are using babel, you should check out babel-plugin-lodash, it will cherry-pick the parts of lodash you are using for you, less hassle and a smaller bundle.
It has a few limitations:
You must use ES2015 imports to load Lodash
Babel < 6 & Node.js < 4 aren’t supported
Chain sequences aren’t supported. See this blog post for alternatives.
Modularized method packages aren’t supported
I just put them in their own file and export it for node and webpack:
// lodash-cherries.js
module.exports = {
defaults: require('lodash/defaults'),
isNil: require('lodash/isNil'),
isObject: require('lodash/isObject'),
isArray: require('lodash/isArray'),
isFunction: require('lodash/isFunction'),
isInteger: require('lodash/isInteger'),
isBoolean: require('lodash/isBoolean'),
keys: require('lodash/keys'),
set: require('lodash/set'),
get: require('lodash/get'),
}
I think this answer can be used in any project easily and brings the best result with less effort.
For Typescript users, use as following :
// lodash.utils.ts
export { default as get } from 'lodash/get';
export { default as isEmpty } from 'lodash/isEmpty';
export { default as isNil } from 'lodash/isNil';
...
And can be used the same way as importing lodash :
//some-code.ts
import { get } from './path/to/lodash.utils'
export static function getSomething(thing: any): any {
return get(thing, 'someSubField', 'someDefaultValue')
}
Or if you prefer to keep the _ to avoid conflicts (ex. map from rxjs vs lodash)
//some-other-code.ts
import * as _ from './path/to/lodash.utils'
export static function getSomething(thing: any): any {
return _.get(thing, 'someSubField', 'someDefaultValue')
}
UPDATE :
Seems like the right way to export is :
export * as get from 'lodash/get';
export * as isEmpty from 'lodash/isEmpty';
export * as isNil from 'lodash/isNil';
...
But there is a weird collision with #types/lodash, I've removed this type package because I would get this error :
Module '"/../project/node_modules/#types/lodash/cloneDeep"' uses
'export =' and cannot be used with 'export *'.ts(2498)
UPDATE :
After some digging, I've turned tsconfig.json feature esModuleInterop to true, and it allows me to do the following :
import get from 'lodash/get';
import isEmpty from 'lodash/isEmpty';
import isNil from 'lodash/isNil';
...
export { get, isEmpty, isNil, ... };
Note that this affects all your imports in your projects that has been defined as import * as lib from 'lib'. Follow the documentation to be sure it's suitable for you.
import { cloneDeep, groupBy } from 'lodash';
I think this is simpler when you don't need to convert array to lodash object by using _.
const groupData = groupBy(expandedData, (x) => x.room.name);
For those who want to keep using _ , then just import them like this:
import groupBy from 'lodash/groupBy';
import filter from 'lodash/filter';
import get from 'lodash/get';
window._ = {groupBy, filter, get};
I think the more cleaner way of importing lodash is just like this:-
import _ from 'lodash'
then you can use what ever you want just by using this underscore just like this:-
_.has()

Categories