Using External Code Components in JS
In JS, any objects, methods or variables used in a code file but not defined in the file itself, must be explicitly imported before they can be used. This would be true for Java, Python and GoLang as well, so no surprise in this respect.
The from
part of JS import statements refers to either an external package listed in package.json
dependencies
, or to a file somewhere in the project. In the latter case, the path to the file must be provided relatively to the location of the file the import is coming to - the file you're writing the import
statement in.
Unlike in Java, where each code component is defined using a dotted path of folders (packages), starting from the code root, in JS code imports use Unix-like file system paths. E.g., in src/relay-mutations/user/create-user-mutation.js
, you can see the following import statements:
import { CompletionObjType } from '../../relay-models/completion-obj-type';
import { createUser } from '../../relay-mutate-and-get/user-mag';
import { logger } from '../../utils/logger';
Matching to the file system tree under src/
, the relative path to get from the destination
src/relay-mutations/user/create-user-mutation.js
to the source
src/relay-models/completion-obj-type
is to move two sub-folder levels up to src/
and then down into src/relay-models
:
../../relay-models/completion-obj-type
The extension .js
is omitted in imports.
Imports may also come from an index.js
file placed into a sub-folder. In that case only the name of the sub-folder (with the path) should be provided , e.g.,
import config from '../config';
actually comes from src/config/index.js
.
Only explicitly exported code components can be imported into other code files.
Export and import rules and syntax have evolved from early versions of JS. In this course we're committed to using modern JS, so we avoid the old style require
in favor of import
- anything that makes JS code look like other professional languages developers are familiar with.
As the ultimate use of imports is to bring in exported code, the key to understanding what is imported is actually on the export side. Let's look at src/data-seed/user-seed.js
:
import mongoose from 'mongoose';
import * as fs from 'fs-extra';
import path from 'path';
import * as yaml from 'js-yaml';
import User from '../db-models/user-model';
import config from '../config';
import { logger } from '../utils/logger';
When using external packages - follow the documentation. However, even though require
is a thing of the distant past, it remains the only documented mode in many packages. E.g., js-yaml
documentation says to use
yaml = require('js-yaml');
A direct replacement of require
with import * as
or just import
usually works:
import * as yaml from 'js-yaml';
The short version, without the *
, is used in the mongoose
import:
import mongoose from 'mongoose';
Digging deeper, however,
import * as <name> from <source>
is not the same as
import <name> from <source>
Again, in those two import
forms, the real outcome is determined by what is on the export side. The asterisk *
in the import
means "bring everything that has been exported", whereas the construction without the *
imports only the so-called default export - identified in the source as export default
. Traditional old-style 3rd party packages use export default
exclusively and do not have any other ("named") exports, therefore, importing with or without the asterisk from those packages brings in the same stuff.
On the export side, the modern recommended approach is to use "named" vs. default exports. E.g., in src/utils/logger.js
, we have:
export const logger = winston.createLogger({
vs.
export default winston.createLogger({
Accordingly, modern 3rd party packages use named export - a separate one for each exported component. This allows importing needed components only vs. the entire content of the package. The import
statement lists the components in curly brackets ({}
). E.g., in src/relay-models/item-price-type.js
:
import { GraphQLFloat, GraphQLObjectType } from 'graphql';
The downside of the above - extra typing. The IDE may help auto-generating imports and it will validate object names, but often times it's the developer's job to type the names in.
Missing imports break your code execution. Extra imports slow down the program load. For the backend developer, it looks like the program load speed is a minor consideration, compare to the front end scenario where all imports must get physically loaded into the browser client over the Internet and compiled/evaluated before the app code flow really starts.
The demo provides examples of import
and export
usage that come from various sources - this would be the case in many production JS projects, though. E.g.,
mongoose
models came from traditional examples, and they use export default
GraphQL examples show how to chain export
and import
from multiple files and folders using intermediate index.js
files
server.js
has require
in a construction that would need to be considerable re-written to use import
instead:
graphQLApp.use('/healthcheck', require('express-healthcheck')());
JS syntax and styling considerations evolve, just pick something you feel comfortable with - you can always change it working on your next project (and you very likely will). And then change again.
Is Imported Code Auto-executed?
If you know a little Python, you've seen how imported code is actually auto-executed in there. Not in many other languages. Not in JS.
However, you may see wierd things happening in JS when some code appears to be kicked off by virtue of importing something from the file the code is located in. This is done by the Evaluation process. The topic is gray enough to keep away from, and the impact of Evaluation on backend JS code is not something you see widely discussed. JS is an interpreted language. NodeJS evaluates modules before running them, and when modules are evaluated, loose code gets executed. E.g., if you put some bare logging statements in JS code files, outside exported blocks, you'll see those statements printed on startup. So, don't put any random loose code into the JS file. Keep the files neat with import statements and export blocks.
After the import
/export
warmup, next, we'll review the demo project JS code components