Why fromClause in import statement expects string literal? - javascript

In our previous node.js application(based on ES5), we struggled for having relative paths in require statements.
for eg: var conf = require('../../global/config');
The issue with the relative paths was bubbled up in code obfuscation. We had to spent considerable time in handling this issue.
With that experience, in our current node.js application(based on ES6), though I'm not sure whether we will obfuscate the code or not I don't want to use relative paths. Hence, I've created a const ROOTDIR in every file and assigned the project's absolute path to it. Then realized that import expects only static literals in it's fromClause. This makes life difficult by forcing to type(copy/paste) the full path for every import or go back using relative paths OR use require statement.
While I'm trying to understand the benefits of using import style, I need some help in understanding the following 3 points.
What are the advantages of using import apart from similar coding style of java/.net?
Why import is accepting only string literals? It seems it's by design, if so, why?
If I want to import a module based on some condition, I'll have to go for require because I can't use import in if/else or switch statements. Is there any solution/hack for it?
Appreciate all the help.

First, remember that import is not currently supported by any environment, including Node.js. So you will probably be using a transpiler, such as Babel, which will assume a CommonJS environment (such as Node) and will fill in using require.
import, unlike require is designed to be statically analysed. That is to say, your code could be analysed without running it and it would be possible to tell what modules need to be loaded.
This means that:
import declarations are hoisted. Wherever they are declared, they effectively exist at the very top of the file.
Putting them in blocks is therefore invalid.
Putting them in conditionals is therefore invalid.
Nothing can exist in the file before the import statement, so it is not possible to use any variables (including constants) because they will not exist when the import statement is run.
Because the code can be statically analysed, this will in the future ease the process of bundling code. It can be determined exactly what code needs to be included, which will mean that your code could be compiled for distribution more easily. For instance, if a browser requested a JS file that had an import statement, the server could see this and package both together. This is a long way off, but it is likely to be the future!

The main difference between import and require is that with import, you have static dependencies which are resolved by the browser when it parses your code, while with require, you have dynamic dependencies which are resolved when Node.js runs your code.
The advantage of static imports is that the code is easier for the JavaScript engine to optimize. webpack for example uses it to remove code from imported modules that are not needed to create a smaller bundle (known as tree shaking).
The downside of this is, as you have noted, that you cannot use a dynamic expression after "from", since this can only be resolved at runtime. So this is indeed by design.
There is no "hack" around it, if you must have a dynamic dependency that is resolved at runtime, you need to use "require".

Related

Benefits of using autoloader with js modules?

In my workplace I've been told by my supervisor to use autoloaders instead of using many import statements since it's slow! First of all, based on my research there are no performance benefits of doing so and secondly(I'm using js modules, and we are not talking about bundles), I can't think of any good reasons for doing such thing.
The way my imports work now are:
Imagine we import a.mjs like so ->
import '/js/components/a.mjs'
The route /js/ is a rule that redirects to my root of static js files, so I don't even need to use relative paths.
Is it bad practice? If there is something that I'm missing anything please tell me; I've done my research and didn't find any reasons to use autoloaders with js modules.
An actual example for you to see:
Flask route rule:
#app.route('/js/<path:filename>')
def js(filename):
return send_from_directory(Path(app.root_path).parent.absolute().joinpath("static\\js"), filename)
and in my js files:
import {View} from "/js/common.mjs";
import Spinner from "/js/components/spinner.mjs";
import RecommendedTripsSlider from "/js/components/recommended-trips-slider.mjs";
import TripsSlider from "/js/components/trips-slider.mjs";
import Blogs from "/js/components/blogs.mjs";
Thank you for your time. I appreciate it.
From the JavaScript Modules Guide on MDN :
Modules are only executed once, even if they have been referenced in multiple <script> tags.
This can be verified in the spec under section 16.2.1.5.2 Evaluate.
Evaluate creates and returns a Promise which resolves when the module has finished evaluating. This Promise is stored in the [[TopLevelCapability]] field of the [[CycleRoot]] for the component. Future invocations of Evaluate on any module in the component return the same Promise.
The first time the browser needs a module, it is hoisted, loaded, cached and executed. Additional import statements in future scripts will reference the pre-evaluated module from memory. No additional HTTP requests need to be made. No additional execution needs to happen.
On subsequent page views, the locally cached version of the module will be used, assuming your server's caching policy is configured appropriately, or you've implemented caching with a service-worker. In fact, you can even preload any specific modules that you might need ahead of time.
Performance-wise, as long as your app isn't importing hundreds of modules you should be fine. At that point you should probably look into bundling related components together with tree shaking. However, monolithic app bundles should be avoided as they obliterate the benefits gained from using modules since one small change to a single component means your users now have to re-fetch the entire app. Keeping your components split up means that user agents only download the parts needed to display the content on the page they're on and nothing more.
One reason you might want to use a module loader/bundler would be if you were installing dependencies through a package manager like npm. Then you would need a way to resolve the named imports to their actual file paths. But that process can easily be handled without a build step by using import-maps.
<script type="importmap">
{
"imports": {
"moment": "/node_modules/moment/src/moment.js",
"yourlib": "/lib/yourlib.js"
}
}
</script>
Unfortunately, this feature is not available in all browsers yet but. This already works in Chromium based browsers, has recently been implemented in Firefox (slated for release in v102) and support for other browsers can be polyfilled with es-module-shims.
This is the future of native module resolution. Implementing an autoloader into your project now would be a step backwards.

Is Entry Point, ES6 Modules

In node, in order to determine if I'm in the entry file, the file referenced directly by node, I can use __file__ === process.argv[1]. process.argv is an array containing the arguments I used to call the node script. Therefore, the first argv is usually node, the second is the file I'm using. This is useful if I'm creating a library and I want to know whether I'm calling the library directly, for example for testing. This would typically get minified out in a production build.
How can I do the same thing in a browser? Before ES6 modules, all JavaScript files were effectively global, meaning the <script> tags were all called directly by the HTML. However, now, with ES6 modules, it is possible to import another JavaScript file. How do I determine whether I am in a file directly sourced by the browser, with <script src="..."></script>, or whether I am in a file that was imported by another script?
This could be useful for react components, especially in libraries. If I am in the "entry" file, I can render myself in a tag that I choose, perhaps <main>. If not, I can assume I'm being imported by another component, which will presumably render itself or be rendered in some other way. The same concept applies to other component-based libraries, like angular 2+, aurelia, etc. For example,
import React from 'react';
import {render} from 'react-dom';
if(isEntryPoint()) {
const main = document.querySelector('main');
render(<App />, main);
}
function App() {
return <div>Hello World!<div>;
}
So how do I find out if a file is directly referenced by the browser via <script src="..."></script>, or if it was imported by another JavaScript file? How should the isEntryPoint function look?
I don't believe there's any reasonable way for a browser-hosted module to know that it's been loaded directly rather than by another module. The unreasonable way I can think to do it is to compare the last segment of the URL in import.meta.url with the script elements present in the DOM and seeing if one matches.
I don't recommend it.
In fact, even then, if for some reason I have
<script type="module" src="./myfile.js"></script>
<script type="module" src="./your-library.js"></script>
and myfile.js does:
import /* stuff */ from "./your-library.js";
That would make your library think it was loaded directly when it fact it was loaded via myfile.js.
So I really don't recommend it. ;-)
Instead, you're probably better off providing your library (your-library.js) with a setup call the code using it can use, and an optional additional module (auto-your-library.js or whatever) that A) loads your library, and B) does the setup call. (If you want to provide the automatic setup feature at all.)
Note: import.meta is new in ES2020.

How do I include untyped-JavaScript, from an adjacent file, in ReasonML bindings?

While trying to get started with Reason, in one JavaScript project, I've got an extremely light file that tries to be a Reason-typed interface to the existing, heavy, library:
/* TheLibrary.re */
type engine
external addEngine : string -> engine -> unit = "" [##bs.val] [##bs.module "../"]
However, when I try to use that library in a ReasonReact project (having added #org/the-library to the bsconfig.json bs-dependencies),
/* AComponent.re */
[#bs.val] [#bs.module "#org/game-engine/dist/game-engine.js"]
external gameEngine : TheLibrary.engine = "default";
/* Further down, a React lifecycle method, */
TheLibrary.addEngine("Game", gameEngine);
I get errors about ../ being not found, relative to that React component:
./src/components/main-menu/AComponent.re
Module not found: Can't resolve '../' in '/Users/ec/Work/reason-reacty/src/components/main-menu'
I've also tried, instead of ../ in TheLibrary.re's external declaration:
#bs.module "./index.js" (the direct, ES6 entry-point for the untyped-JavaScript side of the package in question,)
#bs.module "#org/the-library", the entire name of said library (even though I'm typing inside that library???)
Please help! I'd love to be able to further adopt ML, but I'm having the hardest time wrapping my head around ReasonReact's dependency-resolution!
Additional context:
So, we're trying to build our first ReasonReact project, and we've successfully added baby's-first-opaque-types to one of our internal libraries and include that in the ReasonReact page with something like the following — which works, by the way:
/* Imports.re */
type engine;
[#bs.val] [#bs.module "#org/game-engine/dist/game-engine.js"]
external gameEngine : engine = "default";
[#bs.val] [#bs.module "#org/the-library"] [#bs.scope "default"]
external addEngine : (string, engine) => unit = "";
This yields, when we Imports.(addEngine("Game", gameEngine)), the global setup line we need: TheLibrary.addEngine("Game", GameEngine). I'm in the very first stages of trying to upstream that typing-information into the parent project, and publish that code to npm, so that all consuming projects can start to use Reason.
t sounds like you might be a bit confused about the different tools that make up your toolchain, so let's first go through them one by one to put them in their place:
ReasonReact is a library of opinionated, "thick" bindings to react.js, which despite the name isn't actually all that Reason-specific, except for its integration with Reason's JSX syntax. It would be more accurate to call it a BuckleScript library.
Reason is mostly just the syntax you use, but is often also used more broadly to refer to the ecosystem around it, and usually also imply that BuckleScript is being used.
OCaml is the underlying language. The "semantics" of Reason, if you will.
BuckleScript is the OCaml-to-JavaScript compiler. It compiles ONE source file, which is considered a module, into ONE JavaScript module, but also requires the type information of other OCaml modules as input.
Now, I suspect you already know most of that, but what you do not seem to know is that NONE of these actually do ANY dependency resolution. These next parts of your toolchain are what does that:
The BuckleScript Build System, or bsb, is what finds all the modules in your local project according to what you've specified in src and any BuckleScript libraries you've listed in bs-dependecies in bsconfig.json. It will figure out the dependency order of all these and feed them to the compiler in the correct order to produce one JavaScript module for each OCaml module (along with some other artefacts containing type information and such). But it will not resolve any JavaScript dependencies.
Lastly, webpack, or some other JavaScript bundler, is what you likely use to combine all the JavaScript modules into a single file, and which therefore needs to resolve any JavaScript dependencies. And this is likely where the error message comes from.
Using [#bs.module "some-module"] will make the BuckleScript compiler emit var ... = require('some-module') (or import ... from 'some-module' if es6 is used), but BuckleScript itself will not do anything more with it. The string you pass to #bs.module is the same string you would pass to require if it had been an ordinary CommonJS module (or whatever other module format you have configured).
Also note that the import is not emitted where the external is defined, but where it's used. You can work around, or "ground" it in a module by re-exporting it as an ordinary definition, ie. let addEngine = addEngine.
In order to precisely answer your question I would need to know which bundler you use, where you've configured BuckleScript to output its JavaScript artefacts, where the externals are used, not just defined, and where the external JavaScript module is located. But I hope all this underlying knowledge will make it easy for you and future readers to identify and resolve the problem yourself. If you're still a bit unsure, look at the compiled JavaScript artefacts and just treat them as ordinary JavaScript modules. At this point that's really all they are.

How can I access constants in the lib/constants.js file in Meteor?

I followed the documentation to put the constants in the lib/constants.js file.
Question:
How to access these constants in my client side html and js files?
Variables in Meteor are file-scoped.
Normally a var myVar would go in the global Node context, however in Meteor it stays enclosed in the file (which makes it really useful to write more transparent code). What happens is that Meteor will wrap all files in an IIFE, scoping the variables in that function and thus effectively in the file.
To define a global variable, simply remove the var/let/const keyword and Meteor will take care to export it. You have to create functions through the same mechanism (myFunc = function myFunc() {} or myFunc = () => {}). This export will either be client-side if the code is in the client directory, or server-side if it is in the server directory, or both if it is in some other not-so-special directories.
Don't forget to follow these rules:
HTML template files are always loaded before everything else
Files beginning with main. are loaded last
Files inside any lib/ directory are loaded next
Files with deeper paths are loaded next
Files are then loaded in alphabetical order of the entire path
Now you may run into an issue server-side if you try to access this global variable immediately, but Meteor hasn't yet instantiated it because it hasn't run over the file defining the variable. So you have to fight with files and folder names, or maybe try to trick Meteor.startup() (good luck with that). This means less readable, fragile location-dependant code. One of your colleague moves a file and your application breaks.
Or maybe you just don't want to have to go back to the documentation each time you add a file to run a five-step process to know where to place this file and how to name it.
There is two solutions to this problem as of Meteor 1.3:
1. ES6 modules
Meteor 1.3 (currently in beta) allows you to use modules in your application by using the modules package (meteor add modules or api.use('modules')).
Modules go a long way, here is a simple example taken directly from the link above:
File: a.js (loaded first with traditional load order rules):
import {bThing} from './b.js';
// bThing is now usable here
File: b.js (loaded second with traditional load order rules):
export const bThing = 'my constant';
Meteor 1.3 will take care of loading the b.js file before a.js since it's been explicitly told so.
2. Packages
The last option to declare global variables is to create a package.
meteor create --package global_constants
Each variable declared without the var keyword is exported to the whole package. It means that you can create your variables in their own files, finely grain the load order with api.addFiles, control if they should go to the client, the server, or both. It also allows you to api.use these variables in other packages.
This means clear, reusable code. Do you want to add a constant? Either do it in one of the already created file or create one and api.addFiles it.
You can read more about package management in the doc.
Here's a quote from "Structuring your application":
This [using packages] is the ultimate in code separation, modularity, and reusability. If you put the code for each feature in a separate package, the code for one feature won't be able to access the code for the other feature except through exports, making every dependency explicit. This also allows for the easiest independent testing of features. You can also publish the packages and use them in multiple apps with meteor add.
It's amazing to combine the two approaches with Meteor 1.3. Modules are way easier and lighter to write than packages since using them is one export line and as many imports as needed rather than the whole package creation procedure, but not as dumb-error-proof (forgot to write the import line at top of file) as packages.
A good bet would be to use modules first, then switch to a package as soon as they're tiring to write or if an error happened because of it (miswritten the import, ...).
Just make sure to avoid relying on traditional load order if you're doing anything bigger than a POC.
You will need to make them global variables in order for other files to see them.
JavaScript
/lib/constants.js
THE_ANSWER = 42; // note the lack of var
/client/some-other-file.js
console.log(THE_ANSWER);
CoffeeScript
/lib/constants.coffee
#THE_ANSWER = 42
/client/some-other-file.coffee
console.log THE_ANSWER

What is a typescript AMD module and do I need it for an ASP MVC4 web application?

I started to use Web Essentials and I see there's an option "Use the AMD module". I am using typescript for an ASP MVC4 application. Can someone explain what the AMD module is all
about. Is this something that I should know about?
AMD is one way to format and load modular JavaScript. See here: http://addyosmani.com/writing-modular-js/ and especially here: http://requirejs.org/docs/whyamd.html
To quote from that latter source:
The AMD format comes from wanting a module format that was better than
today's "write a bunch of script tags with implicit dependencies that
you have to manually order" and something that was easy to use
directly in the browser.
Essentially AMD allows you to load JavaScript modules on demand, and provides a format for encapsulating their content so the Global namespace isn't polluted.
In TypeScript, with the AMD compiler switch that you've discovered set to 'on', you export a module like this:
export module pe.components {
export class Component { // 'export' makes this visible outside the module
}
class FriendComponent { // no 'export' so this is only visible inside the module.
}
}
And in another file you import this module like this:
import c = module('relative-path-to-file/pe.components');
And then use it like this:
var component:Component = new c.pe.components.Component(); // Works, because Component is exported
... but not this:
var friend:FriendComponent = new c.pe.components.FriendComponent(); // Shouldn't work* because FriendComponent is not exported.
(* there was a bug that made non-exported interfaces visible outside their declaring modules. I think this has been fixed in TS 0.8.1).
As to the second part of your question - this is really too broad. If your architecture requires you to load new functionality (plug-ins or applets within a single page application, for example), then yes, AMD modules and a loading framework such as RequireJS may be the way to go. If, on the other hand, you know in advance all the functionality your users are going to require, you might be better off just minifying your scripts thoroughly and loading them in advance as a single file.
I don't think the fact that you are working with MVC is relevant here: the question is whether your client-side architecture warrants an asynchronous, modular approach.

Categories