ES6 import declaration + symbol - javascript

What does the + symbol denote in this import declaration?
import app from '+/api';
as opposed to just:
import app from '/api';
The import snippet is from a Node app using es6 which is transpiled with Babel.
./api/index.js exports an Express instance using export default app;
I have also seen this syntax, using a tilde:
import app from '~/api';

The structure of the module identifier or how it is supposed to be interpreted is not part of the ECMAScript specification.
The meaning of the module identifier is defined by the module loader, i.e. the part of the system that actually takes the value and uses it to find the corresponding module.
Of course Node.js has a module loader. It is Node.js that defines that module identifiers are either names of shared modules or paths to files.
However, the + (or ~) here doesn't have any meaning in Node.js. I can only assume that the project might be using a module bundler or something similar which is processing module files and its dependencies. It is that part of the system that defines the meaning of +.
I think this is a point that many people don't understand: While ES6 defines a standard syntax for declaring dependencies, the resolution of the module identifier to the actual module is not part of the specification and may differ greatly between runtimes/environments. Given Node.js' popularity most will likely be compatible with the CommonJS module system (which is what Node uses), but again, there is no standard for that.

Related

When using the ES Module system in Node.js, can all Node packages be imported using the same import syntax?

This question is not whether Node.js supports the ES Module system.
That question has been answered previously, eg:
How can I use an ES6 import in Node.js?
Node.js - SyntaxError: Unexpected token import
And these answers are supported by the official Node.js docs:
ECMAScript modules are the official standard format to package JavaScript code for reuse...Node.js fully supports ECMAScript modules as they are currently specified and provides interoperability between them and its original module format, CommonJS. Node.js has two module systems: CommonJS modules and ECMAScript modules. Authors can tell Node.js to use the ECMAScript modules loader via the .mjs file extension, the package.json "type" field, or the --input-type flag.
Source
So it is clear that Node.js supports the ES Module system.
This question is:
When using the ES Module system in Node.js, can all Node packages be imported using the same import syntax?
The reason I ask is that a lot of packages' documentation does not specify how to import them using ES Module style, eg:
https://expressjs.com/en/starter/hello-world.html
https://github.com/expressjs/session
I want to avoid the situation where I am trying to figure out the correct syntax to use each time I want to import a library.
This answer seems to suggest this syntax:
import * as [ whatever_was_previously_used_as_the_variable_name ] from [ whatever_was_previously_between_the_quotes_in_the_parenthesis ]
So, using the examples above, this:
const express = require('express');
const app = express();
const session = require('express-session');
could safely be converted to:
import * as express from 'express';
const app = express();
import * as session from 'express-session';
Is that the 'universal' syntax that should be used to import all Node packages?
(Update: that session import above doesn't work, at app.use(session(sessionConfig)); it caused the error TypeError: session is not a function - I needed to define the import like this: import session from 'express-session'. I also had to import axios like this: import axios from 'axios', or i got a similar error. However, to use msal-node i had to use import * as msal from '#azure/msal-node').
Just to confuse things, I have come across other syntax examples, such as these in the Node docs (which includes node:):
import * as fs from 'node:fs/promises';
// OR:
import * as fs from 'node:fs';
Source
I am hoping a definitive answer can be provided that covers the different scenarios that may exist, eg: Node packages that live in the node_modules directory as well as one's own custom JavaScript modules that live outside of node_modules.
In this way, if I ever come across a package that does not specify installation instructions using the ES Modules style, I will still know the correct way to import it.
The import statement is indeed universal and can be used to import a module of either system, whether inside node_modules or out, or built-in to Node. When importing a CommonJS module this way, the value it would normally expose to require becomes its default export, so import name from 'url';, or any other form of import statement that retrieves the default export, will get you what you need.
The import * as name from 'url'; form you mentioned is for retrieving all of a module's named exports. This creates a module namespace object, never a function, though the function or other value that a CommonJS module exposes to require is available on the default property. There's generally no need to use import * with a CommonJS module, since everything that Node detects as a named export will be properties on the default export anyways.
You can also create a require from inside an ES module; this will work exactly the same way as require in CommonJS, but that includes the limitation of importing only CommonJS and built-in modules, not ES modules.

Isomorphic JavaScript library using JavaScript modules - Omitting Node.js dependencies in the browser?

So I'm planning on writing a package which a user will hopefully be able to use on both Node.js and in the browser.
On the Node.js side it will use the fs module. This does not exist in the browser perhaps. This could be accomplished with CommonJS with an if clause to check which environment the module is in and a simple require.
This is not the case with import as it is hoisted.
Does anyone have an idea of how to structure the code or the build environment to satisfy both enviroments in the same package?
Stick to ES6 imports
It seems there was an improvement in import support in Node.js. In the docs it says:
The --experimental-modules flag can be used to enable support for
ECMAScript modules (ES modules).
Once enabled, Node.js will treat the following as ES modules when
passed to node as the initial input, or when referenced by import
statements within ES module code:
Files ending in .mjs.
Files ending in .js, or extensionless files, when the nearest parent package.json file contains a top-level field "type" with a
value of "module".
Strings passed in as an argument to --eval or --print, or piped to node via STDIN, with the flag --input-type=module.
The second listed approach may be useable with node.js. Simply make sure your librarie's package.json contains "type": "module" and Node should treat it as a ES6 module instead of CommonJS.
Conditional import as promise - you will have to wait
Conditional imports are already available in node but not actually supported. They seem to work in browser. And there's a caveat: the imported module is a promise. What that means is that you can have partially isomorphic code as long as you plan your steps well. Knowing that you can do import("test.js") in browser and that you can test for window means you can write conditions in a smart way and have cross-platform code.
For example you can do:
if (typeof window !== 'undefined') const FooPromise = import("foo");
But not the opposite. Good luck. Hopefully more support for the es6 dynamic import will come to node.

Writing Common.js modules in Typescript 2.7.2 - compilation questions

I am totally new to Typescript and Javascript as well, so this is undoubtedly a noobie question. I was looking at some Typescript 2.0 tutorials on writing modules. In the typescript documentation, the authors describe both the ES6 module import/export methods, and the CommonJS, AMD, and other import/export methods.
However, it seems that in my tsconfig.json I can specify which module handler I want to use when compiling: AMD, CommonJS, etc.
So my confusion is, can I write the import/exports using the standard ES6 statements with import {}..., and then those get converted to the appropriate CommonJS or RequireJS syntax on compilation? Or do I need to write the appropriate CommonJS/RequireJS, etc. syntax in typescript and then the compilation step will just use whatever I produce?
Yes, it affects the generated code, not how you should write your code.
As a learning exercise, I'd recommend looking at the output with each of the values: None, CommonJS, AMD, System, UMD, ES6, ES2015 and ESNext.
Note that some compiler options aren't available in some configurations:
outFile can only be used with AMD or System
However ES6 / ES2015 may be used when targeting ES5 or lower
can I write the import/exports using the standard ES6 statements with
import {}..., and then those get converted to the appropriate CommonJS
or RequireJS syntax on compilation?
Yes exactly, import and export are part of TypeScript language, and they are virtually identical to import and export in ES6.
TypeScript will compile them according to the combination of module and target settings in tsconfig.json - if target is es6 or above (or if module itself is es6), import and export will remain in generated javascript, otherwise they will get converted to AMD or commonJS syntax.

Multiple TypeScripts Files Configuration

Here comes silly and simple question:
I am new to this whole webpack tools.
I have been trying to build a simple web-app using typescript and webpack. In the old days, I created typescript and compile them without bundling them.
With webpack, I already installed necessary loaders, typescript, and jQuery.
The problem is, I have 3 typescript files:
main.ts -> imports all assets (images, css) and other typescripts
functions.ts -> consist all of my custom functions/modules
ui-controller.ts
in functions.ts I always created namespaces such as:
module Functions{
export module StringTools{
export function IsEmpty(): boolean{
//some code
}
}
}
in the browser, I knew that the snippet code above will be called, but it is not recognized in the main.ts (in the run time) even thou I already import it.
This is how I import it in main.ts:
import '.src/functions'
Any suggestion how I can resolve this?
Typescript module keyword is confusing, and in version 1.5 it was indeed changed to namespace to better reflect it's meaning. Look here.
Namespaces are also called internal modules. They are meant to be used when your files are evaluated at the global scope. You can use typescript playground to see how namespaces are transpiled. The point is - namespaces are not modules.
Webpack however, does not evaluate files in the global scope, it evaluates them inside a function in order to provide real module behavior.
So what does make your typescript file into a module? the keywords export and import (but not inside a namespace like in your example).
Typescript will see those keywords, and will transpile them into commonjs\AMD\es6 require or define statements, according to your configuration in tsconfig.json. Now you have "real" modules. Then it's Webpack's job to do something (lots of info about that online) with those modules so that they will work in the browser where you don't have modules, but that part is not related to typescript.
TL;DR -
So how would you do this in Webpack?
/* functions.ts */
export function IsEmpty(): boolean{
//some code
}
and
/* main.ts */
import {isEmpty} from './functions';
if you want better code organisation as suggested by your use of module StringTools, just split into different files. You can read more about es6 import syntax for more info.
The first part of this answer is that your main module (which has been replaced with the namespace keyword) is not exported... so there are no exported members for your functions file.
export namespace Functions { //...
Part two of the answer is that if you are using modules, you don't need to use namespaces as the module is enclosed within its own scope.
Working version of the file as you designed it, I'd say drop the wrapping namespace:
functions.ts
export namespace StringTools{
export function IsEmpty(): boolean{
//some code
}
}

Do ES6 modules only import what is used?

Is there is a performance or behavioural difference between importing from an index file or importing individual modules?
For example, with an index file (#/modules/user) of...
import routes from './routes'
import controller from './controller'
const user = {
routes,
controller
}
export default user
If I import just the routes from that file...
import user from '#/modules/user'
const routes = Router()
routes.use('/user', user.routes)
Is this any different to just importing the routes individually from their own file (#/modules/user/routes)? Does the controller get imported as it's in the user object?
import userRoutes from '#/modules/user/routes'
const routes = Router()
routes.use('/user', userRoutes)
There are currently no native ES modules in Node.js. The actual difference depends on the toolchain. When the application is built with Webpack/Rollup (a minifier is potentially needed too) and configured to use ES modules internally, tree-shaking can be applied. This is best case scenario.
This would be a case for tree-shaking if there were a reexporting module:
import routes from './routes'
import controller from './controller'
export {
routes,
controller
}
And it was imported like
import { routes } from '#/modules/user'
However, the cases in the original post are different. In one case, once user constant is imported, it's impossible to remove unused controllers property from it with tree-shaking. In another case, #/modules/user/controller module remains unused and doesn't need tree-shaking. It will be ignored even if the application is configured to use CommonJS modules.
So yes, it's possible for ES modules to import only modules that are in use, and this heavily depends on actual code and project configuration.
Client-side applications primarily benefit from tree-shaking because it affects bundle size. This concern shouldn't be taken into account in server-side Node.js application - unless unused module imports massive amount of third-party modules that aren't used anywhere else.
Currently you can only use the import syntax using transpilers(converters from one syntax to another) as it is not yet natively supported by engines. Lets take babel for example.
Babel converts the import to CommonJS style code that can work within Node.js. While the syntax is conformant to ES6, the implementation is not.
Babel converts the syntax into CommonJS, evaluating the imported code before determining what is being imported. With ES6, the imports and exports are determined before the code is evaluated.
With future support of es6 imports inside node, importing a specific function inside the code will only return that exported function, without evaluating the whole script in the targeted file.
Currently they work the same since transpilers convert them to traditional node require syntax.
However, you can use webpack Treeshaking to remove unused code from the output file, this way they are almost identical in behavior.
The # syntax
The # syntax depends on the module loader or module bundler. The module loader is not part of the ECMAScript spec. You most likely have something like babel-plugin-root-import in your webpack/babel config.
Basically it means from the root of the project.. it avoids having to write things like import Component from '../../../../components/component'. in this case, it has nothing to do with partial imports.

Categories

Resources