ClassName/ClassName.js Module Resolution with Webpack & Node.js - javascript

Often times I see the following directory structure and import pattern in Javascript projects:
ClassName/index.js pattern
RepoRoot
|-src
|-index.js
|-ClassName
|-index.js
|-SupportingClass.js
src/ClassName/index.js
import { SupportingClass } from './SupportingClass';
export class ClassName {
// Implementation
}
src/index.js
import { ClassName } from './ClassName';
This is all fine and dandy. It allows for a much cleaner import of the ClassName class. However I'm not a fan of the name of the class file index.js not having the same name as the class's name. It:
Makes it harder to locate the file in a file name search (Command+P in VS Code)
You end up with just a very unhelpful index.js in the file tab in the IDE (yes I know some IDEs are smart enough to add a bit more context when you have more than one file of the same name open. That helps, but isn't a real solution imo)
Other files that aren't the indexes get much more descriptive names: SupportingClass.js, someUtils.js, etc.
ClassName/ClassName.js pattern
RepoRoot
|-src
|-index.js
|-ClassName
|-ClassName.js
|-SupportingClass.js
src/ClassName/ClassName.js
import { SupportingClass } from './SupportingClass';
export class ClassName {
// Implementation
}
src/index.js
import { ClassName } from './ClassName/ClassName';
This solves all of the problems I laid out, but makes the import of ClassName a bit duplicative. Probably not a big deal for most people especially with your IDE being able to auto import things for you nowadays.
However...
Just how webpack & node.js understand out of the box how to interpret the import path:
./ClassName as ./ClassName/index.js
Is there a setting or plugin that allows for it to interpret:
./ClassName as ./ClassName/ClassName.js?
I've been wondering for years if such a thing existed but could never really find any literature on it. Curious if anyone here knows of anything.

You are dealing with named imports and default imports, more details on this discussion can be found here:
How to import both default and named from an ES6 module
From your situation (I haven't tried with subfolders, but it should work), you may want to organize your files as such:
RepoRoot
|-src
|-index.js
|-ClassName
|-ClassName.js (named module)
|-SupportingClass.js (named module)
|-index.js (default modules)
And then you can easily import your stuff in src/index.js such as:
import ClassName, SupportingClass, { otherValues } from './ClassName'

Related

Exporting one set of functions from multiple files

I've just finished writing a big quickbooks wrapper in JavaScript with a lot of parts in different files, e.g. a file for tax, a file for accounts, a file for auth, etc. I'd like to make it so that developers using this wrapper will only have to do one import that will contain all the functions I've written.
Hierarchy is something like this:
QbTax.js
QbAccounts.js
QbAuth.js
I'd like to have another file, or a way that a developer using this wrapper would only have to import one thing, then be able to call all functions from the above files from that one import.
Something like this:
import * as qb from './unifiedQbFile.js';
qb.thisIsATaxFunc();
qb.thisIsAnAccountsFunc();
qb.thisIsAnAuthFunc();
What is the best way to approach this?
The only idea I have at the moment is to write prototypes in a file (unifiedQbFile.js for instance) and export those. I'd import all the functions from my other files in that unified file then call them in my new prototypes. That seems messy though.
you can have one index.js file that exports all files then you can import that index.js file. I would personally go with this method.
// index.js
export { qbTaxFunc } from 'QBTax';
export { qbAccountsFunc } from 'QbAccounts';
export { qbAuthFunc } from 'QbAuth';
Now in some other script to import it you can do.
import qb from 'path-to-index.js';
// All functions should now be available
qb.qbTaxFunc();
qb.qbAccountsFunc();
qb.qbAuthFunc();

reexport all files from a folder webpack

We're using a folder structure like this
components
| Button.js
| Nav.js
| ...etc
| index.js
somefolder
|somefile.js
in the inderx file we're importing every component and reexporting it like this
// index.js
import Button from './Button'
import Nav from './Nav'
export {Button, Nav}
this way we can import many components into a file like this
// somefile.js
import {Button, Nav} from '../components'
Maintaining that index file is a bit of a pain though and discourages flexible use of components. I know that Webpack can import many files with a syntax like this
function requireAll(r) { r.keys().forEach(r); }
requireAll(require.context('./components/', true, /\.js$/));
however, I didn't yet find a way to reexport all of these components to use them like above.
The desired outcome is to replace the index.js file with something that automates the process of bundling all the files from a folder without having to add every file manually.
I think getting rid of the file index will not be the best solution and may cause questions from other developers. But I can offer a slightly more simplified way:
index.js:
export * from './some-component1.js';
export * from './some-component2.js';
some-component1.js:
export {SomeComponent1};
some-component2.js:
export {SomeComponent2};

Dynamically reference static ESNext imports

Say I have these imports:
import clearLineReporter from '../modules/clear-line-reporter';
import karmaReporter from '../modules/karma-reporter';
import metaTestReporter from '../modules/meta-test-reporter';
import stdReporter from '../modules/std-reporter';
import tapJSONReporter from '../modules/tap-json-reporter';
import tapReporter from '../modules/tap-reporter';
import webSocketReporter from '../modules/websocket-reporter';
these must be referenced like I do above, in other words, I obviously can't do this:
const imports = {
stdReporter: import(...),
tapJSONReporter: import(...),
...
webSocketReporter: import(...)
}
Is there any way I can reference imported files through some form of reflection? Because it seems like I can't group them together to reference them somehow.
Instead of import syntax, I could use require(), but I am wondering if there is some way I can do some dynamic things with import statements, for example reference them all dynamically, such that if I add or remove an import, I don't have to change any other code.
There is a great answer to this question that I discovered by asking a different question, here:
exporting imports as a namespace with TypeScript
Create a file name grouped-modules.ts for example, where you want to simply list only the modules and export each one.
export {default as clearLineReporter} from '../modules/clear-line-reporter';
export {default as karmaReporter} from '../modules/karma-reporter';
export {default as metaTestReporter} from '../modules/meta-test-reporter';
...
export {default as stdReporter} from '../modules/std-reporter';
export {default as tapJSONReporter} from '../modules/tap-json-reporter';
Then in your module you can just do :
import * as mods from './grouped-modules'
export {mods}
It will export both types and values in a namespace called s. You can then import them using :
import {mods} from 'your-module'
const anObject: mods.clearLineReporter = ...;
This allows you to dynamically group your modules into one variable.
Is there any way I can reference imported files through some form of reflection?
Answer is dependent on environment, meant in questing, because import statement can be ES native modules implementation in browser, or babel-ed to require statements in node.js, or compile-time resolved bindings in webpack.
So, in each case there is solution to do something reflection. In node.js with babel-ed code import is just require wrapper, so any information is available there.
In browser with native ES modules, all requests to them can be served via ServiceWorker, so it can provide necessary information about fetched ES modules. Also in browser ES modules can be dynamically imported that way: https://matthewphillips.info/posts/loading-app-with-script-module
Most interesting part is webpack: compile-time resolve and semi-reflection can be achieved by externals resolver in functional style (https://webpack.js.org/configuration/externals/#function), and runtime by load module API (https://webpack.js.org/api/module-variables/#webpack_modules-webpack-specific- )

Should I use both `import 'rxjs/Rx'` and `import { Observable } from '#rxjs/Observable'`

import { Injectable } from '#angular/core';
import { Headers, Http, Response } from '#angular/http';
import { Observable } from '#rxjs/Observable';
import 'rxjs/Rx';
import 'rxjs/add/observable/throw';
#Component({});
export shellModule{}
This is a piece of code form my Angular app that I copied from somewhere (I have removed the definitions in the exported module. I am using it to make a service to call APIs.
In the imports in this particular file, why is it that Observable is imported separately even though the entire rxjshas been imported. If a particular module is being imported in its entirety, why is a particular object from it imported separately? I tried asking this question at the forum from where I took it, but there was no answer. I want to understand if this somehow helps with optimization of code.
In general:
In Typescript, the way modules are handled would require you to either load in the entire library with the import * as rx from 'rxjs/Rx', or a specific exported module within the library to use it, so the the compiler loads in the types.
Reducing your imports to only the specific modules you need sets up your app to use tree shaking from Angular's AOT compilation. This is not done by the typescript compiler, but by a tool called rollup. So, it can help with optimizing code later, but it doesn't automatically do so.
As far as compilation overhead, bringing in the whole library might slow down the compiler a bit... but this isn't a very strong point except for massively complex libraries.
I, personally, prefer importing in specific modules because it makes the calling code a little cleaner since I don't need to use that global name to get to the specific name. rx.Observable vs Observable. A good example of this is the lodash library (rxjs is a bit more complex...)
Honestly, importing entire libraries like the line you have there: import 'rxjs/Rx' doesn't make sense to me. You should only import specific exported modules. Try removing it, seeing what errors you get, and then using the * as rx syntax instead.
As far as rxjs goes - it is a little wonky when you want to import specific operators like this question does - so the way to get specific operators is with: import 'rxjs/add/observable/from' - but that also requires a tinkering with your webpack set up as outlined in the referenced question's answer.
Let's see what the rxjs/Rx module exports:
export { Subject, AnonymousSubject } from './Subject';
export { Observable } from './Observable';
export { Operator } from './Operator';
export { Observer } from './Observer';
export { Subscription } from './Subscription';
export { Subscriber } from './Subscriber';
export { AsyncSubject } from './AsyncSubject';
export { ReplaySubject } from './ReplaySubject';
export { BehaviorSubject } from './BehaviorSubject';
...
import './add/observable/bindCallback';
import './add/observable/bindNodeCallback';
import './add/observable/combineLatest';
...
So it exports RxJs classes and also imports operators from the add folder. So as you can see it loads everything in the library. It doesn't export any global object though. So you need to use named export like this:
import * as Rx from 'rxjs/Rx'
to be able to use an exported class:
Rx.Observable.of(12, 3);
This emulates what you would have if you loaded the library using the bundle - a global Rx object:
<script src="rxjs/bundles/Rx.js">
If you want to use Observable without Rx global object, you need to import it separately:
import { Observable } from '#rxjs/Observable';
Observable.of(1);
Importing both
import { Observable } from '#rxjs/Observable';
import 'rxjs/Rx';
is not a good practice, but may be used if you don't want to import every operator separately.
Also see How to correctly import operators from the rxjs package.

Import shared/partial module into another file

Say I have two files:
box.ts:
export module Entities {
export class Box { .. Stuff }
origin.ts:
export module Entities {
export class Origin { .. other stuff }
As I understand javascript, those two modules should merge into one module that has both my classes in it.. (I just want them in separate files for organizational reasons, but both box and origin are entities.)
So now I have a file that needs to use both of those:
service-actions.ts:
import {Entities} from '../entities/box';
import {Entities} from '../entities/origin';
... stuff that uses both of those entities.
This gives me the error:
Duplicate identifier 'Entities';
If I have one or the other, it works fine, but both fail.
Is there a way I can combine these so that the I can do Entities.Box and Entities.Origin in my service-action.ts file?
Is the only way to do this is to put Box and Origin in the same file?
Don't mix namespaces (internal modules) with external modules. See this post and this question and others for arguments against mixing. I've found it best to not use internal modules altogether. We don't gain any encapsulation benefits from namespaces. Instead, directories are a simple and solid way to organize code, and you are using them already.
entities/box.ts:
export class Box { .. Stuff }
entities/origin.ts:
export class Origin { .. other stuff }
Now instead of having to write Entities.Box, you can just import Box and use it:
import { Box } from '../entities/box';
import { Origin } from '../entities/origin';
Use as:
import {Entities as BoxEntities} from './../entities/box';
import {Entities as OriginEntities} from './../entities/origin';
// Use BoxEntities and OriginEntities here
Preferably, if you are actually exporting the classes directly from the files as you should be (and as pointed out by mk.), you could create an entities.ts file that handles it for you:
export * from './entities/box';
export * from './entities/origin';
Then import this file:
import * as Entities from './../entities';
// use Entities.Box and Entities.Origin here

Categories

Resources