Can't set values on `process.env` in client-side Javascript - 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).

Related

Best practices for creating a "debug mode" variable for my app?

I was about to comment out blocks of code that just printed/console.logged debugging info, and I thought, why don't I create a global scope "debug" variable, and instead of commenting this code out, put an if (DEBUG == 1) {} around it?
The reason I ask is because I'm working with javascript at the moment, and my code is spread across a few .js files. If I create a DEBUG variable in app.js, I'll need to export it from app.js and require it in other files; is this consistent with best practices? Is there a better way to do what I'm thinking of?
There are many ways to do this. Most logging libraries will have levels that allow you to only output or see messages whose levels are above some minimum. Alternatively, if you're just using console.log or console.debug and content to keep those in lieu of more robust log streams, you can change the behavior of these by using your own logging library; for example, if you have a debug.js file that exports your debug() function, import/require it once in each other file and just call debug() instead of console.debug() (or you can actually reassign console.debug = debug but that will have potential side effects in any dependencies or dependent code).
In debug.js, your function can simply check an environment variable (in node.js or similar) or global variable (in the browser) or even a hard-coded flag, and immediately return (doing nothing) if you're in production or not in the mood to print debug messages.
Take a look at bunyan's log levels as an example of how a popular logging library handles this: https://www.npmjs.com/package/bunyan#levels
If you are programming the browser and you want a quick and dirty global variable, you can do window.myVar = 'whatever'.

Debugging JavaScript code that uses ES6 Modules

TL;DR: How can I access variables/functions/names that are defined in ES Modules from the debugger?
More context: I'm a relatively experienced JavaScript programmer, but new to Modules. I've followed the tutorial at MDN here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules. They have a good set of examples here: https://github.com/mdn/js-examples/tree/master/modules
In that collection, say in the "basic-modules" example, (live code here: https://mdn.github.io/js-examples/modules/basic-modules/) there is, for example, a function called random in the file modules/square.js. Suppose I want to execute that function in the debugger, just to try it out, or because it's my code and I want to test/debug it, or I want to demonstrate to another coder what the function does. All the stuff you expect to do in a REPL or debugger. Is there a way to do that? I've tried both the Firefox debugger and the Chrome debugger, with no luck.
Back in the pre-Modules era, that code would be put into the global namespace (making access easy) or it would be locked up in an IIFE (making access impossible) or maybe in some home-made module system (access depends). I am hoping that the new Modules system still allows the debugger access to the names inside modules.
Thanks.
It says in the docs:
Last but not least, let's make this clear — module features are imported into the scope of a single script — they aren't available in the global scope. Therefore, you will only be able to access imported features in the script they are imported into, and you won't be able to access them from the JavaScript console, for example. You'll still get syntax errors shown in the DevTools, but you'll not be able to use some of the debugging techniques you might have expected to use.
To take your example from before, you'll need to invoke that function from a scope where it is visible, i.e where it's been imported:
import { random } from 'path/to/square.js'
debugger; // you should be able to invoke random() from here

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.

Is there a way to tell Google Closure Compiler to *NOT* inline my local functions?

Here's what I'm looking for:
I want to use the wonderful features of SIMPLE mode minification while disabling just one specific feature (disable local function inline).
UPDATE: The answer is NO, it's not possible given my setup. But for me there is a workaround given I am using Grails.
As #Chad has explained below, "This violates core assumptions of the compiler". See my UPDATE3 below for more info.
IN QUESTION FORM:
I'm using CompilationLevel.SIMPLE_OPTIMIZATIONS which does everything I want, except that it's inlining my local functions.
Is there any way around this? For example, is there a setting I can place in my JS files to tell Google Closure not to inline my local functions?
It would be cool to have some directives at the top of my javascript file such as:
// This is a JS comment...
// google.closure.compiler = [inlineLocalFunctions: false]
I'm developing a Grails app and using the Grails asset-pipeline plugin, which uses Google Closure Compiler (hereafter, Compiler). The plugin supports the different minification levels that Compiler supports via the Grails config grails.assets.minifyOptions. This allows for 'SIMPLE', 'ADVANCED', 'WHITESPACE_ONLY'.
AssetCompiler.groovy (asset-pipeline plugin) calls ClosureCompilerProcessor.process()
That eventually assigns SIMPLE_OPTIMIZATIONS on the CompilerOptions object. And by doing so, CompilerOptions.inlineLocalFunctions = true as a byproduct (this is hard coded behavior in Compiler). If I were to use WHITESPACE_ONLY the result would be inlineLocalFunctions=false.
So by using Asset Pipeline's 'SIMPLE' setting, local functions are being inlined and that is causing me trouble. Example: ExtJS ext-all-debug.js which uses lots of local functions.
SO post Is it possible to make Google Closure compiler *not* inline certain functions? provides some help. I can use its window['dontBlowMeAway'] = dontBlowMeAway trick to keep my functions from inlining. However I have LOTS of functions and I'm not about to manually do this for each one; nor would I want to write a script to do it for me. Creating a JS model and trying to identity local functions doesn't sound safe, fun nor fast.
The previous SO post directs the reader to https://developers.google.com/closure/compiler/docs/api-tutorial3#removal, where the window['bla'] trick is explained, and it works.
Wow thanks for reading this long.
Help? :-)
UPDATE1:
Okay. While spending all the effort in writing this question, I may have a trick that could work. Grails uses Groovy. Groovy makes method call interception easy using its MetaClass API.
I'm going to try intercepting the call to:
com.google.javascript.jscomp.Compiler.compile(
List<T1> externs, List<T2> inputs, CompilerOptions options)
My intercepting method will look like:
options.inlineLocalFunctions=false
// Then delegate call to the real compile() method
It's bed time so I'll have to try this later. Even so, it would be nice to solve this without a hack.
UPDATE2:
The response in a similar post (Is it possible to make Google Closure compiler *not* inline certain functions?) doesn't resolve my problem because of the large quantity of functions I need inlined. I've already explained this point.
Take the ExtJS file I cited above as an example of why the above similar SO post doesn't resolve my problem. Look at the raw code for ext-all-debug.js. Find the byAttribute() function. Then keep looking for the string "byAttribute" and you'll see that it is part of strings that are being defined. I am not familiar with this code, but I'm supposing that these string-based values of byAttribute are later being passed to JS's eval() function for execution. Compiler does not alter these values of byAttribute when it's part of a string. Once function byAttribute is inlined, attempts to call the function is no longer possible.
UPDATE3: I attempted two strategies to resolve this problem and both proved unsuccessful. However, I successfully implemented a workaround. My failed attempts:
Use Groovy method interception (Meta Object Protocol, aka MOP) to intercept com.google.javascript.jscomp.Compiler.compile().
Fork the closure-compiler.jar (make my own custom copy) and modify com.google.javascript.jscomp.applySafeCompilationOptions() by setting options.setInlineFunctions(Reach.NONE); instead of LOCAL.
Method interception doesn't work because Compiler.compile() is a Java class which is invoked by a Groovy class marked as #CompileStatic. That means Groovy's MOP is not used when process() calls Google's Compiler.compile(). Even ClosureCompilerProcessor.translateMinifyOptions() (Groovy code) can't be intercepted because the class is #CompileStatic. The only method that can be intercepted is ClosureCompilerProcessor.process().
Forking Google's closure-compiler.jar was my last ugly resort. But just like #Chad said below, simply inserting options.setInlineFunctions(Reach.NONE) in the right place didn't resurrect my inline JS functions names. I tried toggling other options such as setRemoveDeadCode=false to no avail. I realized what Chad said was right. I would end up flipping settings around and probably destroying how the minification works.
My solution: I pre-compressed ext-all-debug.js with UglifyJS and added them to my project. I could have named the files ext-all-debug.min.js to do it more cleanly but I didn't. Below are the settings I placed in my Grails Config.groovy:
grails.assets.minifyOptions = [
optimizationLevel: 'SIMPLE' // WHITESPACE_ONLY, SIMPLE or ADVANCED
]
grails.assets.minifyOptions.excludes = [
'**ext-all-debug.js',
'**ext-theme-neptune.js'
]
Done. Problem solved.
Keywords: minify, minification, uglify, UglifyJS, UglifyJS2
In this case, you would either need to make a custom build of the compiler or use the Java API.
However - disabling inlining is not enough to make this safe. Renaming and dead code elimination will also cause problems. This violates core assumptions of the compiler. This local function is ONLY referenced from within strings.
This code is only safe for the WHITESPACE_ONLY mode of the compiler.
Use the function constructor
var fnc = new Function("param1", "param2", "alert(param1+param2);");
Closure will leave the String literals alone.
See https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Function

Javascript Closure compiler -Exporting global variables

My webapp is based on a common script where I define the common functions and a global variable and dynamically loaded scripts that process those. So far, the only way I found to export the global variable is to replace any occurrence by window["myGlobalVar"] but I find it very ugly. Is there a better way to do?
Here is an illustration
// commonscript.js before compilation
function incrementVariable() {window["myGlobalVar"]++;}
window["incrementVariable"] = incrementVariable;
window["myGlobalVar"] = 0;
and in another script
alert(myGlobalVar); // <= alerts 0
incrementVariable();
alert(myGlobalVar); // <= alerts 1
I am looking for a way to use directly myGlobalVar in both files because it would be more elegant. However, I would need to set window["myGlobalVar"] to a pointer and not a copy of the object and I am not sure how to do that on simple types.
Is it possible? Is encapsulating myGlobalVar in an Object the only other way?
Thanks a lot for your lights.
New Answer
Closure-compiler supports an #nocollapse annotation which prevents a property from being collapsed to a global variable. This allows the property to be mutable when exported.
#nocollapse does not block renaming - you still need to export a property to accomplish that.
#nocollapse is currently only supported when compiling from source. It will be included in the next release - that is versions AFTER the v20150315 release.
Old Answer
#expose is now deprecated. The compiler will warn about any usage of #expose
There is a new, but so far undocumented, annoatation: #expose. This single annotation will both export a property and prevent it from being collapsed off a constructor. It sounds like the perfect fit for your situation - but it will require your variable to be a property on an object.
However, use with care. Any properties which have #expose will not be renamed and will not be removed as dead code. This makes it especially problematic for use by javascript library writers.
If you want to have a variable that doesn't get renamed, just create a file called for example props.txt with the following contents:
myGlobalVar:myGlobalVar
Then when compiling your code, add the command line argument: --property_map_input_file props.txt
Your variable will not be renamed and is available to all scripts as long as it's not optimized away. Also, if you don't declare it at all (so you omit var myGlobalVar), it won't get renamed or removed.

Categories

Resources