Is there a `declare` keyword in ES6/7? - javascript

Recently I've been studying ES6, and that lead to me using Babel a lot. Being the curious type, I started looking at the Babel Github repository to know how they built this awesome tool, and know if I can somehow contribute.
However, I came across this file, and it has things like declare class BabelNodeSourceLocation {} written all over it, and the file ends with .js.
This got me very confused, and I am now wondering whether there's a declare keyword in JavaScript that I didn't know of, or is this just a Babel-specific syntax? All my Google searches resulted in nothing.
Update: Putting the code in the Babel REPL resulted in nothing. Babel just ignored the code and did not produce any equivalent ES5 output. It also did not throw any error.

and the file ends with .js.
That doesn't mean a lot these days :-)
I am wondering whether there's a declare keyword in JavaScript that I didn't know of
No, there is not.
Or is this just a Babel-specific syntax?
No. It is a type declaration file for the Flow typechecker.

With Flow, you can declare a global class that allows you to reference the class type anywhere in your project. This has no affect on runtime code and won't affect babel output.
An example from the docs:
declare class URL {
constructor(urlStr: string): URL;
toString(): string;
static compare(url1: URL, url2: URL): boolean;
};
And then in your project you can reference URL as a class type.
Similarly, you can declare other global Types, Modules, Functions, Variables. A good way to keep them organized.

Related

How does the browser know I'm targeting ES6?

I'm using Typescript and I recently changed the transpilation options to target ES6 instead of ES5.
I was using a certain syntax that had always worked fine under ES5, but after the target change, I started getting this error in the (Firefox) browser console:
Javascript ES6 TypeError: Class constructor Client cannot be invoked without 'new'
I changed the code to a suitable syntax for ES6, and this fixed the issue, but I don't understand how the browser would know to throw this error in the first place, because the exact same code worked before.
Does the javascript parser in browser scan other parts of the code base and see they they're using ES6, and then reject this line because it doesn't match the ES6 code elsewhere?
Javascript doesn't enter any particular "mode".
Likely what really happened is that you were declaring a class Client. That is ES6 syntax, and it comes with the special caveat that you must use new Client to instantiate it. Now, your compiler compiled this into a backwards-compatible function Client() .... Obviously, you can call a function without new. And that's what you were doing somewhere.
So, the compilation from class to function masked the error. But when leaving the class as class (because ES6 target mode does not need to dumb it down to a function), the browser was actually dealing with a class and raised that error.

How encapsulation works in TypeScript

I am trying to understand how encapsulation works in Typescript and come up with an example which makes me confusing why am I able to access and even change directly the private members of a certain class.
class Encapsulate {
str:string = "hello"
private str2:string = "world"
}
var obj = new Encapsulate();
console.log(obj.str); //accessible
obj.str2 = "something else";
console.log(obj.str2); //compilation Error as str2 is private
OUTPUT: hello something else
I am getting a compile time warning like compilation Error as str2 is private, but still I am able to change it or access it. Am I missing the concept of Encapsulation in general, what it is and how it works on Typescript.
The problem is that typeScript just compiles to javaScript, but it doesn't have a runtime.
TypeScript is giving you those warnings at compilation type. It is telling you:
Ey, Encapsulate str2 is private, and you're still trying to access it. You shouldn't do that.
However, once ts compiles and generates the js code, all the typescript annotations are lost in the code. JavaScript does not know anything about private, enum or interfaces. Once your code gets compiled and then runs in a browser or any other js runtime, you'll have an Encapsulate javaScript object with two fields, str1 and str2. They cannot be private or public because js does not know about that. Encapsulate will be a plain bare javaScript object.
So, basically, typeScript helps you catch errors at compile time. But, once the code compiles, it will 'forget' everything about typeScript annotations.
It can tell you that you shouldn't access str2. It can even refuse to compile if such an error is found (this depends upon tsconfig configurations). But the compiled code won't have anything to do with typescript.
This is expected as TypeScript is just a static type checking language.
Once compiled to js, the operation you are performing is valid, hence no runtime error
Javascript has no concept of private and public members for a class (though this might change in the future). At this time marking a property or function in a class as private is only checked at compile time (this is why you get a syntax error).
Your code will still run though because even though Typescript generated a syntax error it also most likely still outputted valid Javascript code.

Can't set values on `process.env` in client-side Javascript

I have a system (it happens to be Gatsby, but I don't believe that's relevant to this question) which is using webpack DefinePlugin to attach some EnvironmentVariables to the global variable: process.env
I can read this just fine.
Unfortunatley, due to weirdnesses in the app startup proces, I need have chosen to do some brief overwritting of those EnvironmentVariables after the site loads. (Not interested in discussing whether that's the best option, in the context of this question. I know there are other options; I want to know whether this is possible)
But it doesn't work :(
If I try to do it explicitly:
process.env.myVar = 'foo';
Then I get ReferenceError: invalid assignment left-hand side.
If I do it by indexer (which appears to be what dotenv does) then it doesn't error, but also doesn't work:
console.log(process.env.myVar);
process.env['myVar'] = 'foo';
console.log(process.env.myVar);
will log undefined twice.
What am I doing wrong, and how do I fix this?
The premise behind this attempted solution was flawed.
I was under the impression that webpack "made process.env.* available as an object in the browser".
It doesn't!
What it actually does is to transpile you code down into literals wherever you reference process.env. So what looks like fetch(process.env.MY_URL_VAR); isn't in fact referencing a variable, it's actually being transpiled down into fetch("http://theActualValue.com") at compile time.
That means that it's conceptually impossible to modify the values on the "process.env object", because there is not in fact an actual object, in the transpiled javascript.
This explains why the direct assignment gives a ref error (you tried to execute "someString" = "someOtherString";) but the indexer doesn't. (I assume that process.env gets compiled into some different literal, which technically supports an indexed setter)
The only solutions available would be to modify the webpack build process (not an option, though I will shortly raise a PR to make it possible :) ), use a different process for getting the Env.Vars into the frontEnd (sub-optimal for various other reasons) or to hack around with various bits of environment control that Gatsby provides to make it all kinda-sorta work (distasteful for yet other reasons).

Ignore not declared variables (TypeScript)

I am creating a WebExtension using TypeScript which is later compiled to JavaScript.
My extension depends on one of the APIs the browser (Firefox) offers, specifically the extension API. As an example, I use the getURL() method, which is called like this:
browser.extension.getURL("foo/bar.js");
Of course, TypeScript gives an error "Cannot find name 'browser'". This prevents me from fully compiling the code. I would like to know if there is any way to bypass this. Preferably not only at compile level, but also at the linting level.
I have tried:
Defining browser at the beginning as var browser: any;: breaks the API.
Compiling with --noEmit, --noEmitOnErrors: irrelevant, still complains.
Any suggestions?
If you want to let Typescript know that the variable exists but not actually emit any code for it you can use declare
declare var browser: any;

qx.log.appender Syntax

When declaring qx.log.appender.Native or qx.log.appender.Console, my IDE (PyCharm) complains about the syntax:
// Enable logging in debug variant
if (qx.core.Environment.get("qx.debug"))
{
qx.log.appender.Native;
qx.log.appender.Console;
}
(as documented here)
The warning I get is
Expression statement is not assignment or call
Is this preprocessor magic or a feature of JavaScript syntax I'm not aware yet?
Clarification as my question is ambiguous:
I know that this is perfectly fine JavaScript syntax. From the comments I conclude that here's no magic JS behavior that causes the log appenders to be attached, but rather some preprocessor feature?!
But how does this work? Is it an hardcoded handling or is this syntax available for all classes which follow a specific convention?
The hints how to turn off linter warnings are useful, but I rather wanted to know how this "magic" works
Although what's there by default is legal code, I find it to be somewhat ugly since it's a "useless statement" (result is ignored), aside from the fact that my editor complains about it too. In my code I always change it to something like this:
var appender;
appender = qx.log.appender.Native;
appender = qx.log.appender.Console;
Derrell
The generator reads your code to determine what classes are required by your application, so that it can produce an optimised application with only the minimum classes.
Those two lines are valid Javascript syntax, and exist in order to create a reference to the two classes so that the generator knows to include them - without them, you wouldn't have any logging in your application.
Another way to create the references is to use the #use compiler hint in a class comment, eg:
/**
* #use(qx.log.appender.Native)
* #use(qx.log.appender.Console)
*/
qx.Class.define("mypackage.Application", {
extend: qx.application.Standalone,
members: {
main: function() {
this.base(arguments);
this.debug("Hello world");
}
}
});
This works just as well and there is no unusual syntax - however, in this version your app will always refer to the those log appenders, whereas in the skeleton you are using the references to qx.log.appender.Native/Console were surrounded by if (qx.core.Environment.get("qx.debug")) {...} which means that in the non-debug, ./generate.py build version of your app the log appenders would normally be excluded.
Whether you think this is a good thing or not is up to you - personally, these days I ship all applications with the log appenders enabled and working so that if someone has a problem I can look at the logs (you can write your own appender that sends the logs to the server, or just remote control the user's computer)
EDIT: One other detail is that when a class is created, it can have a defer function that does extra initialisation - in this case, the generator detects qx.log.appender.Console is needed so it makes sure the class is loaded; the class' defer method then adds itself as an appender to the Qooxdoo logging system
This is a valid JS syntax, so most likely it's linter's/preprocessor's warning (looks like something similar to ESLint's no-unused-expressions).
Edit:
For the other part of the question - this syntax most likely uses getters or (rather unlikely as it is a new feature) Proxies. MDN provides simple examples of how this works under the hood.
Btw: there is no such thing as "native" JS preprocessor. There are compilers like Babel or TypeScript's compiler, but they are separate projects, not related to the vanilla JavaScript.

Categories

Resources