ES2015 modules, should I import / export everything? - javascript

I started using import and export in javascript recently, but I am a bit confused. I'm not exactly sure what I'm supposed to export, and what I'm supposed to keep local to the file. I don't know how to express this nice in english, so here is a small section from my code:
The /client/js/Inventory.js file, module:
import {
$,
renderHTML,
game
} from "../main.js";
const template = {
titanium: {
name: "Titanium",
description: "Description here..."
}
}
export default class Inventory {
constructor(inventory) {
this._name = inventory.name;
this._description = inventory.description;
}
get name() {return this._name}
get description() {return this._description}
generate(id) {
renderHTML("inventory, `
<div class="inventory" id="${id}">
content here...
</div>
`);
}
static make(id) {
game.inventory[id] = new Inventory(template[id]);
game.inventory[id].generate(id);
}
and my /client/main.js file looks something like this:
import Inventory from "./js/Inventory.js";
const $ = (id) => document.getElementById(id);
const renderHTML = (id, str) => $(id).insertAdjacentHTML("beforeend", str);
const game = {
inventory: {}
};
Inventory.make("titanium");
export {
$,
renderHTML,
game
};
Since I'm exporting game object from the main.js file, does that mean a new game object is being created in the Inventory.js file after it's imported, or does that mean that the Inventory.js file can now access main.js file's game object?
Since I'm calling the classes inside the main.js file, do I also need to export the template object from the Inventory.js file, and import it inside the main.js file?
Exporting only the class works just fine, but I don't get how is the main.js accessing the template if it's not exported? Does it look in the Inventory.js file if none was found in the main.js or?
Thanks!

It's pretty simple. Modules are a structured way of sharing code or data between different pieces of code.
You export any functions or data that you wish to share with other modules that will load your module.
If you have no intention of sharing it or no need to share it, don't export it. If you do need to share it or some other module needs to be able to access it, you export it. Export is the means of sharing with other other modules.
You import any properties from other modules that you need to use in this module.
For imports, you only import what you need now. No need to import something you "might" need in the future. You can always just add the import at the time you actually need it.
For exports, you only export what you specifically intend to share now. If you find a need to share more later, you can always add another export later.
Code that is only used within this module does not need to be exported. In fact, one of the benefits of modules is that you can maintain code privacy or protection in a module because other code cannot access anything in a module that is not exported or shared somehow via an export.
You can logically think of exports as the "api" for your module. This is what other modules can call in your module.
You can logically think of imports as you specifying what "apis" you want to use from other modules.
When everything is in the global namespace (as with the original browser design), then there was no explicit export or import. Everything declared at the top level was just public and shared. This caused all sorts of problems, particularly as projects got larger and there got to be more and more files and then got even more complicated when you started trying to use third party code.
The module system is a structured way of saying that, by default, everything is private. You then explicitly export only the things you want to share. And, then when someone wants to use your apis, they explicitly import the apis they want to use. Each module lives in it's own scope so has its own namespace. Modules also make a very natural testable unit.
Before the standarization of Javascript modules, developers had build a whole bunch of different conventions to try to work-around the large flat global namespace in Javascript. It was not uncommon to encounter multiple conventions in the same project if you were using 3rd party libraries. For developers not trying to use a convention to solve this, code could get pretty messy with lots of potential for variable naming conflicts, accidental replacement of functions and a generally undocumented web of dependencies between files, etc... The standardized module design attempts to provide one common way of addressing these issues and, in the process, also make it a lot easier to write reusable, testable, shareable code.
Since I'm exporting game object from the main.js file, does that mean a new game object is being created in the Inventory.js file after it's imported, or does that mean that the Inventory.js file can now access main.js file's game object?
It means that Inventory.js can now export the one game object that was created in main.js. There is no implicit copying when you export or import.
Since I'm calling the classes inside the main.js file, do I also need to export the template object from the Inventory.js file, and import it inside the main.js file?
You only need to export things from inventory.js that you directly need to reference from some other file. Since main.js does not need to reference the template variable directory, there is no need to export it. The act of importing from inventory.js loads and runs that module. That makes the template variable available to all the code inside inventory.js which is all your code needs. So, no need to export it.
Exporting only the class works just fine, but I don't get how is the main.js accessing the template if it's not exported? Does it look in the Inventory.js file if none was found in the main.js or?
Exporting the Inventory class allows any other module to use that class and all its methods. The process of importing anything from inventory.js cause the module itself to get loaded so all the variables defined within inventory.js are active inside of inventory.js. When you create an Inventory object via the exported class, you are running code in inventory.js that has access to all the data in that module.
Thing of import as two steps. First load the module that is referenced (if it's not already loaded). Then, fetch the exports that you requested imports for.

Related

Instantiate an object defined in module A and use its methods in module B javascript

I'm working in a web development project.
Right now i'm using a 3rd party library to instantiate an object of that library in a file called, let's say, fileA.js
so i do:
import libraryExport from "./librarymain.js"
var object = libraryExport( ... );
export default object;
Now, in fileB.js i want to use the methods that the instantiated object has, for example:
import object from "fileA.js"
object.methodOfTheLibrary();
However, when im running this in my browser console i always get "methodOfTheLibrary is not a function", which means, from my point of view, that the library is not being imported properly in fileB.js
Note: I'm using webpack to bundle all of my files and everything was compiling and bundling just fine until i came with issues. I usually know my way around C++ in an advanced way but for JS i just still don't fully understand how to solve these kind of import issues.
Thank you for any help
It's generally* recommended that you avoid using an imported module directly in the body of another module. (One of your own modules, that is... as you'll see in a moment, third-party modules are generally fine to use.)
The big issue is load order. If your fileA also imports some other module, which imports another module which then imports fileB, then fileB will attempt to run and, since fileA is still trying to load dependencies, object will not actually have been instantiated yet.
You'll either need to carefully review your dependencies to look for just such a loop and then eliminate it or, if possible, restructure fileA to wrap your code in a function that can be called once the entry point has finished loading (which will guarantee that all other modules have been resolved):
// fileB.js
import object from "fileA.js"
export function init() {
object.methodOfTheLibrary();
}
// main.js
import init from "fileB.js";
init();
* "generally" meaning that there are plenty of perfectly acceptable situations where it's fine. You just have to mindful of the pitfalls and mitigate against those situations.

Use all files in a folder like a one big JS

Is it possible to configure ESLint in WebStorm so functions, variables, etc. are parsed also from files in the same folder? In my build process, I concatenate all files in the same folders into big closures, for example:
src/
main/ ===> "main.js"
api.js
init.js
ui.js
constants.js
.
.
renderer/ ===> "renderer.js"
core.js
events.js
I would like ESLint to treat all those files just like one, so I don't get "undef" errors for things that are defined.
If it can't be done automatically, I wouldn't mind to create a manual configuration specifying all those files if that is possible.
EDIT: Why I don't (can't) use modules? TLDR- legacy code and project requirements.
I need to minify all code. Current closure compiler can transpile ES6 into ES5, but I found some ES6 features very prone to produce broken code. So I am forced to use ES5.
As I need ES5. I would only be able to use require() to use modules. Now that's a problem, as require() is a dynamic include and it impacts performance on my context (big electron app for modest power devices)
So to answer #Avin_Kavish, I agree what I do is "technically non conforming", but at the end of the build process it is, because each folder has been grouped into a file. That file is the module or the script. To group the files I use a Gradle plugin https://github.com/eriwen/gradle-js-plugin, I inject a "closure header" and a "closure footer", and all the files in between in the order I want.
Despite the inconvenience, at the end I get super-compact nodeJS code, with all methods obfuscated, etc.
I ended up using #Patrick suggestion, thanks for that!
EDIT 2
WebPack + Electron-WebPack turned out to be what I was looking for.
BTW- I think the proper way to do this is if EsLint would allow a "folder" sourceType.
You didn't provide code examples in your question, but I assume you do something like this:
api.js
const api = {
fetchData() {
// some code that fetches data
}
};
core.js
const core = {
init() {
api.fetchData();
}
};
The ESLint rule that causes errors when you lint these JavaScript modules is the no-undef rule.
It checks for variables that are used without having been defined. In the code example core.js above, this would be api, because that is defined in another module, which ESLint doesn't know about.
You don't care about these errors, because in your actual JS bundle used in production, the code from api.js and core.js is concatenated in one bundle, so api will be defined.
So actually api in this example is a global variable.
The no-undef rule allows you to define global variables so that they won't cause errors.
There are two ways to do this:
Using Comments
At the beginning of your core.js module, add this line:
/* global api */
Using the ESLint Config
As explained here – add this to your .eslintrc file:
{
"globals": {
"api": "writable"
}
}
Side Note
As some commenters to your question pointed out, it would probably be better to use import and export statements in the modules, together with a module bundling tool like webpack to create one bundle from your JavaScript modules.
A physical JavaScript file with an import/export statement is a module by the standard. A single .js file without import/export is a script by the standard. What you are trying to do is non-conforming to this, there is no specification in ECMAScript that allows splitting a single script or module across several files. I do get where you are coming from, for example: C# has partial classes that allows you to split a class across multiple files. But trying to replicate this without a standard syntax is not wise. Especially, when import/export can and will do the job for you
For example, with the following assumptions, your main.js can be refactored to,
constants.js // <--- constants
ui.js // <--- logic to build UI
api.js // <--- exposing public api
init.js // <--- setup code before use
// main.js
// If you name this index.js you can import it as 'src/main' instead of 'src/main/main.js'
import { A,B } from './constants'
import { api } from './api'
import { displayUi } from './ui'
import { init } from './init'
init(A);
displayUi(B);
export { api } // <-- re-expose public api

What's the difference between Javascript import and angular import?

I am new to Angular world. There is something which confuses me while learning it, why we need to import any module two times : once through Javascript 'import' statement and then putting it in 'import' array?
Why need to import the same thing two times? Same goes with other code parts : need to first import 'component' and then again need to put the same in 'declarations' array of #NgModule.
Why need to do that? I am not getting it.
I'm guessing you're talking about your module files ?
First, you have this line
import { MyComponent } from './my-component.component';
this line allows the typescript compiler to say
Okay, I need the resources from that file, in this file.
In this case, you're importing a class.
The next line is
declarations: [MyComponent]
(Or imports or modules or providers etc.)
In this case, this is related to Angular : as you can see, you put those "imports" into the decorator of your module, #NgModule. This is internal Angular stuff, but it allows him to do the correct things with your classes. For instance, when you put injectable classes into the providers, it tells Angular to create single instances of thoses classes.
The keyword "import" is actually tells to import a module into the current module(module has class in it).
But after #NgModule whatever we are importing using imports keyword those are only single instances of the modules/classes imported previously.
Please correct me if I am wrong.

Babel.js using Import and Export not working

I'm trying to use import and export to create modules and it's not working.
I added https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.24.0/babel.min.js to the index.html header and tried to import a js file and get an error message saying SyntaxError: import declarations may only appear at top level of a module. What can I possibly be doing wrong?
I know I can use require.js but rather use import and export.
HTML
script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.24.0/babel.min.js"></script
JS File
import Mymodule from './modules/mymodule';
Babel cannot perform client-side transpiling of modules, or rather it is not universally supported by browsers. In fact, unless you use a plugin, Babel will transform import into require().
If I run the following code:
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.24.0/babel.js"></script>
<script defer type="text/babel" data-presets="es2015">
import Mymod from './modules/module';
Mymod();
</script>
</head>
I get the following error:
Uncaught ReferenceError: require is not defined
From Babel Docs:
Compiling in the browser has a fairly limited use case, so if you are working on a production site you should be precompiling your scripts server-side. See setup build systems for more information.
Most people choose a pre-compiled module bundler like Webpack or Rollup.
If you really want to perform this client-side, use RequireJS with Babel run via a plugin, though you may need to use AMD syntax.
Native browser support for ES6 modules is still in early stages. But to my knowledge there isn't a preset/plugin available yet for Babel to tell it not to transform import/export statements.
The scripts that babel-standalone translates execute by default in global scope, so any symbols defined by them are automatically available to every other module. From that perspective, you don't need import/export statements in your modules.
However, you might be trying to maintain source files that can be used both by babel-standalone (e.g. for quick test environments, feature demonstrations, etc) and via bundlers such as webpack. In that case, you need to keep the import and export statements there for compatibility.
One way to make it work is to add extra symbols into the global scope that cause the import and export code that babel generates to have no effect (rather than causing an error as usually occurs). For example, export statements are compiled into code that looks like this:
Object.defineProperty (exports, "__esModule", {
value: true
});
exports.default = MyDefaultExportedClass;
This fails if there is no existing object called "exports". So give it one: I just give it a copy of the window object so anything interesting that gets defined is still accessible:
<script>
// this must run before any babel-compiled modules, so should probably
// be the first script in your page
window.exports = window;
import statements are translated to calls to require(). The result (or properties extracted from it) is assigned to the variable used as the identifier in the import statement. There's a little bit of complication around default imports, which are different depending on whether or not the result of require() contains the property __esModule. If it doesn't, things are easier (but then you can't support having both default and named exports in the same module ... if you need to do this, look at the code babel produces and figure out how to make it work).
So, we need a working version of require(). We can provide one by giving a static translation of module name to exported symbol/symbols. For example, in a demo page for a React component, I have the following implementation:
function require (module) {
if (module === "react") return React;
if (module === "react-dom") return ReactDOM;
}
For a module returning multiple symbols, you'd just return an object containing the symbols as properties.
This way, a statement like
`import React from "react";`
translates to code that is effectively:
`React = React;`
which is roughly what we want.

How to use js modules from non-module files

I'm a beginner at using js modules.
I'm working on a fairly simple web application. It uses typescript and angular 2, which heavily relies on modules.
Most of my app ts files 'import' one or many js modules (usually mostly angular 2 modules).
As I understand, because my app ts files have a top level 'import', they are automatically considered a js module by typescript.
However, I want any of my app ts files to be accessible by any other of my app ts files, without having to 'import' each other. But because they are now modules themselves, ts requires me to do that...
Is it possible?
It seems crazy to me that for each of my app ts file, I should have to declare every other of my app ts files that are used in there (I like to have tiny files with a single class/interface). In addition, this relies on relative paths which breaks as soon as I restructure my folder structure.
Am I thinking about this the wrong way?
You must have a js file which is an entry point to your application right?.. So in that file just import all the modules which you want to access without importing and attach them to the window object. Since the window object is available globally, you can access your module from anywhere without importing the corresponding module. For example,
Consider this scenario:
You have a module in a file called module1.ts
The entry point of your application is a file called index.ts
And you have a module2 where you require something from module1
// module1.ts
function add(first: number, second: number): number {
return first + second
}
export {add}
in your index.ts
// index.ts
import {add} from '<path to module1>/module1';
window.add = add
Now in your module2
// module2.ts
window.add(1, 2)
Since the window object is available globally you can attach as many properties to it as you like.
As far as the type resolution is concerned you can declare a window module with the add function you require in a .d.ts file as follows:
declare module window {
add: (first: number, second: number) => number
}
Declaring dependencies (e.g modules) for each file is a double-edged sword.
The advantage is that there is no 'magic' - you know exactly where each function, variable, class etc. is coming from. This makes it much easier to know what libraries / frameworks are being used and where to look to troubleshoot issues. Compare it to opposite approach that Ruby on Rails uses with Ruby Gems, where nothing is declared and everything is auto-loaded. From personal experience I know it becomes an absolute pain to try to workout where some_random_method is coming from and also what methods / classes I have access to.
You're right that the disadvantage is that it can become quite verbose with multiple imports and moving relative files. Modern editors and IDEs like WebStorm and Visual Studio Code have tools to automatically update the relative paths when you move a file and also automatically add the imports when you reference code in another module.
One practical solution for multiple imports is to make your own 'group' import file. Say you have a whole bunch of utility functions that you use in all your files - you can import them all into a single file and then just reference that file everywhere else:
//File: helpers/string-helpers.ts
import {toUppercase} from "./uppercase-helper";
import {truncate} from "./truncate-helper";
export const toUppercase = toUppercase;
export const truncate = truncate;
Then in any other file:
import * as StringHelpers from "../path-to/helpers/string-helpers";
...
let shoutingMessage = StringHelpers.toUppercase(message);
The disadvantage of this is that it may break tree shaking, where tools such as webpack remove unused code.
Is it possible
Not in any easy way. The ts file is a module and uses e.g. module.exports (if commonjs) that will need to be shimmed out. And that is just the runtime story. The TypeScript story will be harder and one way would be to make a .d.ts file for the module stating the contents as global.
Like I said. Not worth doing. Modules are the way forward instead of making something hacky.
It's not crazy at all. You are definitively thinking in the wrong way.
Actually what you don't like it's a common feature in all modern programming languages and it makes the code and structure of the app a lot clearer and simple to understand.
Without imports and going to old school way looks very crazy to me :)
You can have only chaos with so many global variables.

Categories