I'm developping a JS-app that needs to work both on the client side and the server side (in Javascript on a browser and in Node.js), and I would like to be able to reuse the parts of the code that are used for both sides.
I have found out that window was a variable only accessible on Browsers, and global in node, so I can detect in which environment the code is executing (assuming that no script declares the window variable)
They are two problems.
How should I detect in which browser the code is running. For example, is this code OK. (This code is inline, meaning that it is surrounded by some global code, reused for both environments)
if window?
totalPath= "../examples/#{path}"
else
totalPath= "../../examples/#{path}"
How can I use global variables for both environments ? Now, I'm doing the following, but this really doesn't feel right.
if window?
window.DocUtils = {}
window.docX = []
window.docXData= []
else
global.DocUtils= {}
global.docX = []
global.docXData = []
NOTE: This question had two parts, but because the title was "Environment detection: node.js or browser" - I will get to this part first, because I guess many people are coming here to look for an answer to that. A separate question might be in order.
In JavaScript variables can be redefined by the inner scopes, thus assuming that environment has not created variables named as process, global or window could easily fail, for example if one is using node.js jsdom module, the API usage example has
var window = doc.defaultView;
After which detecting the environment based on the existence of window variable would systematically fail by any module running under that scope. With the same logic any browser based code could easily overwrite global or process, because they are not reserved variables in that environment.
Fortunately there is a way of requiring the global scope and testing what it is - if you create a new function using a new Function() constructor, the execution scope of this is bound to the global scope and you can compare the global scope directly to the expected value. *)
So to create a function check if the global scope is "window" would be
var isBrowser=new Function("try {return this===window;}catch(e){ return false;}");
// tests if global scope is bound to window
if(isBrowser()) console.log("running under browser");
And function to test if global scope is bound to "global" would be
var isNode=new Function("try {return this===global;}catch(e){return false;}");
// tests if global scope is bound to "global"
if(isNode()) console.log("running under node.js");
the try... catch -part will makes sure that if variable is not defined, false is returned.
The isNode()could also compare this.process.title==="node" or some other global scope variable found inside node.js if you will, but comparing to the global should be enough in practice.
http://jsfiddle.net/p6yedbqk/
NOTE: detecting the running environment is not recommended. However, it can be useful in a specific environment, like development and testing environment which has some known characteristics for the global scope.
Now - the second part of the answer. after the environment detection has been done, you can select which environment based strategy you want to use (if any) to bind your variable which are "global" to your application.
The recommended strategy here, in my opinion, would be to use a singleton pattern to bind your settings inside a class. There is a good list of alternatives already in SO
Simplest/cleanest way to implement a singleton in JavaScript
So, it may turn out if you do not need a "global" variable, and you do not need the environment detection at all, just use the singleton pattern to defined a module, which will store the values for you. OK, one can argue that the module itself is a global variable, which in JavaScript it actually is, but at least in theory it looks a bit cleaner way of doing it.
*) https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function
Note: Functions created with the Function constructor do not create
closures to their creation contexts; they always are created in the
global scope. When running them, they will only be able to access
their own local variables and global ones, not the ones from the scope
in which the Function constructor was called.
Since apparently Node.js could have both (w/ NW.js?), my personnal way to do it is by detecting if the node entry exists in process.versions object.
var isNode = false;
if (typeof process === 'object') {
if (typeof process.versions === 'object') {
if (typeof process.versions.node !== 'undefined') {
isNode = true;
}
}
}
The multilevel of conditions is to avoid errors while searching into an undefined variable due to some browsers limitations.
Reference: https://nodejs.org/api/process.html#process_process_versions
There is an npm package just for this and it can be used both on client-side and server-side.
browser-or-node
You can use it this way
if (isBrowser) {
// do browser only stuff
}
if (isNode) {
// do node.js only stuff
}
Disclaimer: I am the author of this package :)
You can attach to variable window or global - based on situation. Though it is not a recommended way of making multi-platform JS application:
var app = window ? window : global;
It is much better to have your global variable, that will be used over logic of application, but will be made of parts of based on different platforms. Something like:
var app = {
env: '',
agent: ''
};
if (window) {
app.env = 'browser';
app.agent = navigator.userAgent;
} else if (process) {
app.env = 'node';
}
So the idea is that your main application logic will be absolutely the same and will use same object, only that global object have to be changed based on environment. That makes your application much more portable and flexible in terms of platforms.
I know this is a late answer to a (1.5 year) old question but why not copy jQuery's source code?
if (typeof module === "object" && typeof module.exports === "object") {
// node
}
if (typeof window !== "undefined" && typeof window.document !== "undefined") {
// browser
}
Good luck.
this seems to work well regardless of scope unless you've named something else window
const isBrowser = () => typeof window !== `undefined`
if (isBrowser())
console.log(`is browser`)
else
console.log(`is node.js`)
/*
detect global/window/self in browser, deno, nodejs
including where 'this' is undefined
*/
const self = new Function('return this')(); // be careful, Function is like eval, use with caution
console.log(
(self.window && "window" || self.global && 'global'),
self.toString().slice('[object '.length, -1).toLowerCase()
);
/*
browser: window window
nodejs: global global
deno: window object
*/
Old question with many complicated answers, even an npm package, but this solution is quite simple and robust, unless sabotaged on purpose (no solution is 100% precise BTW, because you can set global variables on both environments)
if (typeof process === 'object' && String(process) === '[object process]') {
// is Node
} else {
// is Browser
}
Normally (almost always) scripts which run on browsers don't have the global process object, and even if you create one by accident with process = {}, it will fail in the second condition.
I am not totally familiar with the Node environment and all its situations such as when Babel or WebPack is being used. But this is one way if you have code that runs in the browser vs in the Node console:
if (this.window) {
// inside browser
} else {
// inside Node
}
Simple condition from pdf.js
Second condition variant process.constructor.name === 'process'
src/shared/is_node.js:
/* Copyright 2018 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* globals process */
// NW.js / Electron is a browser context, but copies some Node.js objects; see
// http://docs.nwjs.io/en/latest/For%20Users/Advanced/JavaScript%20Contexts%20in%20NW.js/#access-nodejs-and-nwjs-api-in-browser-context
// https://www.electronjs.org/docs/api/process#processversionselectron-readonly
// https://www.electronjs.org/docs/api/process#processtype-readonly
const isNodeJS =
typeof process === "object" &&
process + "" === "[object process]" &&
!process.versions.nw &&
!(process.versions.electron && process.type && process.type !== "browser");
export { isNodeJS };
This is what I'm using based on #TeroTolonen answer:
var isBrowser = (function() {
try {
return this === window;
} catch (e) {
return false;
}
})();
if (isBrowser) {
}
There is no need for a function constructor and you can call it once.
Related
I am trying to write a package for server and client use with as little modification as needed.
Some libs are part of Node but not included in a browser, others are available in a browser but not in Node.
Using https://github.com/lmaccherone/node-localstorage for example, I want to require the library when on Node, but in a browser, localStorage is already available.
I would like to check whether localStorage is available with a declare statement found in https://stackoverflow.com/a/65755447/2875404 so it looks like that:
declare var localStorage: any | undefined;
if (typeof localStorage === "undefined" || localStorage === null) {
console.log("need to use Node localStorage")
var LocalStorage = require('node-localstorage').LocalStorage;
var localStorage = new LocalStorage('./localStorage');
} else {
console.log("using Browser localStorage")
}
After wrapping up the package in webpack and, for a test, running it in a Chrome snippet, the "need to use Node localStorage" message pops up in console. If I manually edit the webpack'd code to console.log(localStorage) it actually does print the localStorage (so it must be there) - additionally, when I remove the whole if (typeof... block, the code accessing localStorage seems to run just fine.
What exactly do I need to do to make this "hybrid decision" function work? Can this have to do with webpack somehow putting things into a "deeper" scope that doesn't have access to window variables such as localStorage? But why is it possible to print and interact with the class then? I'm confused.
As I said in the comment, you could use the global window to check if you're in browser context:
declare var localStorage: any | undefined;
if (typeof window === 'undefined') {
console.log("need to use Node localStorage")
var LocalStorage = require('node-localstorage').LocalStorage;
var localStorage = new LocalStorage('./localStorage');
} else {
console.log("using Browser localStorage")
}
Recently I have come across about globalThis in Javascript. I am not sure how it is going to behave if it is called from a function. Every time it is returning the window object. if that is the case, then why don't we directly use the window object. What is necessary of using globalThis?
If I call the from a function, then it is returning window object
Example:
(function test(){
console.log(globalThis); // returns window
})();
var obj = {
key1: function(){
console.log(globalThis)
},
key2: ()=>{
console.log(globalThis)
},
key3: function(){
var arrFn = () => {
console.log(globalThis);
}
arrFn();
}
};
obj.key1(); // returns window object
obj.key2(); // returns window object
obj.key3(); // returns window object
I believe the internal implementation of globalThis is like the below code:
const getGlobalThis = () => {
if (typeof globalThis !== 'undefined') return globalThis;
if (typeof self !== 'undefined') return self;
if (typeof window !== 'undefined') return window;
if (typeof global !== 'undefined') return global;
// Note: this might still return the wrong result!
if (typeof this !== 'undefined') return this;
throw new Error('Unable to locate global `this`');
};
const theGlobalThis = getGlobalThis();
Can anyone please explain to me the exact use case of the globalThis? What will be the ideal scenario to use this?
As MDN says:
The global globalThis property contains the global this value, which is akin to the global object.
Why it's useful:
Historically, accessing the global object has required different syntax in different JavaScript environments. On the web you can use window, self, or frames - but in Web Workers only self will work. In Node.js none of these work, and you must instead use global.
The globalThis property provides a standard way of accessing the global 'this' value (and hence the global object itself) across environments. Unlike similar properties such as window and self, it's guaranteed to work in window and non-window contexts. In this way, you can access the global object in a consistent manner without having to know which environment the code is being run in. To help you remember the name, just remember that in global scope the this value is globalThis.
If you don't know for certain what environment the code is going to be run in, or don't feel like keeping track of it (less cognitive overhead is a good thing, after all!), you can use globalThis instead.
If you know for sure what environment your code is going to be running in, and that the code will never be ported to a different environment, feel free to just keep using window (or the appropriate other property for the environment's global object).
My self-directed javascript study has finally led me to reading libraries where I found the following snippet (truncated for brevity). I'm using Firefox with firebug on a windows apache server (xampp).
I figured the snippet below would suffice but if anyone needs it, the entire library can be found here: snap.svg.js on github
var Snap = (function(root) {
Snap.version = "0.4.0";
function Snap(w, h) { // can be either width, height OR
if (w) {
if (w.nodeType) { // deterimines if parameter is a dom element
return wrap(w);
}
if (is(w, "array") && Snap.set) { // deterimines if parameter is an array
return Snap.set.apply(Snap, w);
}
if (w instanceof Element) { // deterimines if parameter is a Snap.Element
return w;
}
if (h == null) { // by elimination determines if parameter is a dom element id.
w = glob.doc.querySelector(String(w));
return wrap(w);
}
}
<numerous public and private properties and methods>
.
.
.
glob.win.Snap = Snap;
return Snap;
}(window || this));
Firebug shows the Snap object in the window context before instantiating any user objects. I was wondering exactly what mechanism was injecting the Snap object into the DOM. That's when I noticed the "var Snap". Initially, I thought that was it. But, since it didn't break the app when I changed the variable name or even deleted it, I became confused.
Further investigation resulted in the discovery at the bottom of the iife... specifically, "glob.win.Snap = Snap". Since "window" is being passed into the iife, it seems this is what's actually creating the Snap object in the window. Changing the name to glob.win.Snappy confirmed this.
I'm still learning so please correct me if I'm wrong. I'm trying to understand what's going on with this library. It seems that the function Snap() is being injected to the window context via the glob.win.Snap assignment. I don't see that the "var Snap" at the top or the "return Snap" are doing anything. In fact, I can rem them out and everything seems to function fine. So, my first question: Do those two lines serve some function I'm not seeing?
A secondary question is: What does the "this" fallback parameter refer to? My limited understanding of Snap is that it is always used within the window namespace so wouldn't "this" always be the window?
Just when I think I'm beginning to make the paradigm shift from classical to prototypical language, I run across code like this and it sets me back. I'd really appreciate some insight.
I had a look at the referenced source code, here a more condensed version:
var Snap = (function(root) {
Snap.version = "0.4.0";
function Snap(w, h) {}
var glob = {
win: root.window,
doc: root.window.document
};
...
glob.win.Snap = Snap;
return Snap;
}(window || this));
Snap.plugin(...);
It seems that the function Snap() is being injected to the window
context via the glob.win.Snap assignment. I don't see that the "var
Snap" at the top or the "return Snap" are doing anything.
You are correct, the declaration of var Snap = ...; and assignment via return Snap; is superfluous, since that variable lives in the global scope (i. e. the Window object) and is already declared by glob.win.Snap = Snap;
I assume they just keept that var declaration since it is pretty much standard when using the class pattern:
var MyClass = (function() {
function MyClass(n) {}
return MyClass;
})();
A secondary question is: What does the "this" fallback parameter refer
to? My limited understanding of Snap is that it is always used within
the window namespace so wouldn't "this" always be the window?
In some JavaScript environments, the root object is not called window (e.g. global in node.js). window || this will evaluate to the root object, no matter what it is called.
You will see such dependency injections often in JavaScript modules. See https://carldanley.com/js-module-pattern/ for more.
However, it seems that this library will not run if there is no window object available due to the var glob = { win: root.window, ... } assignment. They might just have kept the this in there because it is part of a standard module pattern.
I am working on a JavaScript library for JSON/XML processing. My library works in browser as well as Node.js (with xmldom and xmlhttprequest modules).
One of the users recently asked for RequireJS support. I have taken a look at the RequireJS/AMD thing and think it is a good approach so I'd like to provide this.
However I'd like to retain the portability: my library must work in browsers (with and without RequireJS) as well as Node.js. And in the browser environment I don't depend on xmldom or xmlhttprequest since these things are provided by the browser itself.
My question is: how can I implement my library so that it works in browsers as well as in Node.js with an without RequireJS?
A bit of historyand my current solution
I initially wrote my library for browsers. So it just created a global-scope object and put everything inside it:
var Jsonix = { ... };
Later on users asked for Node.js support. So I added:
if(typeof require === 'function'){
module.exports.Jsonix = Jsonix;
}
I also had to import few modules mentioned above. I did it conditionally, depending on whether the require function is available or not:
if (typeof require === 'function')
{
var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;
return new XMLHttpRequest();
}
Now there's this story with RequireJS. If RequireJS is present then the require function is present as well. But module loading works differently, I have to use the define function etc. I also can't just require things since require has an async API in RequireJS. Moreover, if my library is loaded via RequireJS, it seems to process the source code and detects require('something') even if I do it conditionally like
if (typeof require === 'function' && typeof require.specified !== 'function) ...
RequireJS still detects require('xmlhttprequest') an tries to load the corresponding JS file.
Currently I'm coming to the following solution.
// Module factory function, AMD style
var _jsonix = function(_jsonix_xmldom, _jsonix_xmlhttprequest, _jsonix_fs)
{
// Complete Jsonix script is included below
var Jsonix = { ... };
// Complete Jsonix script is included above
return { Jsonix: Jsonix };
};
// If require function exists ...
if (typeof require === 'function') {
// ... but define function does not exists, assume we're in the Node.js environment
// In this case, load the define function via amdefine
if (typeof define !== 'function') {
var define = require('amdefine')(module);
define(["xmldom", "xmlhttprequest", "fs"], _jsonix);
}
else {
// Otherwise assume we're in the RequireJS environment
define([], _jsonix);
}
}
// Since require function does not exists,
// assume we're neither in Node.js nor in RequireJS environment
// This is probably a browser environment
else
{
// Call the module factory directly
var Jsonix = _jsonix();
}
And this is how I check for dependencies now:
if (typeof _jsonix_xmlhttprequest !== 'undefined')
{
var XMLHttpRequest = _jsonix_xmlhttprequest.XMLHttpRequest;
return new XMLHttpRequest();
}
If I have require but not define then I assume this is a Node.js environment. I use amdefine to define the module and pass the required dependencies.
If I have require and define thet I assume this is a RequireJS environment, so I just use the define function. Currently I also assume this is a browser environment so dependencies like xmldom and xmlhttprequest are not available and don't require them. (This is probably nor correct.)
If I don't have the require function then I assume this is a browser environment without RequireJS/AMD support so I invoke the module factory _jsonix directly and export the result as a global object.
So, this is my approach so far. Seems a little bit awkward to me, and as a newbie to RequireJS/AMD I'm seeking advise. Is it the right approach? Are there better ways to address the problem? I'd be grateful for your help.
Take a look at how underscore.js handles it.
// Export the Underscore object for **Node.js**, with
// backwards-compatibility for the old `require()` API. If we're in
// the browser, add `_` as a global object.
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = _;
}
exports._ = _;
} else {
root._ = _;
}
...
// AMD registration happens at the end for compatibility with AMD loaders
// that may not enforce next-turn semantics on modules. Even though general
// practice for AMD registration is to be anonymous, underscore registers
// as a named module because, like jQuery, it is a base library that is
// popular enough to be bundled in a third party lib, but not be part of
// an AMD load request. Those cases could generate an error when an
// anonymous define() is called outside of a loader request.
if (typeof define === 'function' && define.amd) {
define('underscore', [], function() {
return _;
});
}
This is what I ended up with:
// If the require function exists ...
if (typeof require === 'function') {
// ... but the define function does not exists
if (typeof define !== 'function') {
// Assume we're in the Node.js environment
// In this case, load the define function via amdefine
var define = require('amdefine')(module);
// Use xmldom and xmlhttprequests as dependencies
define(["xmldom", "xmlhttprequest", "fs"], _jsonix_factory);
}
else {
// Otherwise assume we're in the browser/RequireJS environment
// Load the module without xmldom and xmlhttprequests dependencies
define([], _jsonix_factory);
}
}
// If the require function does not exists, we're not in Node.js and therefore in browser environment
else
{
// Just call the factory and set Jsonix as global.
var Jsonix = _jsonix_factory().Jsonix;
}
Here is a template I'm currently using, it's both AMD and node compatible though not directly loadable stand-alone in the browser...
The main advantage to this approach is that the domain-specific code does not need to care about what imported it, for the general case.
/**********************************************************************
*
*
*
**********************************************************************/
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)(
function(require){ var module={} // makes module AMD/node compatible...
/*********************************************************************/
/*********************************************************************/
/**********************************************************************
* vim:set ts=4 sw=4 : */ return module })
So I'm building a node module for use with node-webkit that creates a new object and exports it. Standard fare. But since Node has no access to the nw-gui module of node-webkit, I'm just passing it in as a parameter to the constructor. Something like this:
function Example(gui) {
this.gui = gui; //Save for later
}
Example.prototype.createExampleMenu = function() {
return new this.gui.Menu();
}
exports.example = Example;
Works great. But I'm trying to modify .prototype methods of node-webkit's inner modules, like Menu and MenuItem. Is the only way to modify those methods (or add new ones) in the constructor itself? If I try to add new prototype methods outside, it (obviously) fails since this.gui hasn't been set. Basically, I'm trying to make it nicer to add new prototype methods to node-webkit modules without doing it in the constructor. Anyone?
I'm in no way an expert but from what I understand of the implementation of node-webkit from reading its source code, I doubt you can modify any of the objects defined in nw.gui.
If you look at the implementation of Node's standard require function in a running node-webkit instance, you'll find:
function (name) {
if (name == 'nw.gui')
return nwDispatcher.requireNwGui();
return global.require(name);
}
which means that requires of nw.gui are very special indeed.
Rather than requiring JavaScript code, this returns an internal binary object that only appears to be a required library.
Looking a little deeper, we find the nwDispatcher.nwGui.Menu is defined as:
function Menu(option) {
if (typeof option != 'object')
option = { type: 'contextmenu' };
if (option.type != 'contextmenu' && option.type != 'menubar')
throw new String('Invalid menu type: ' + option.type);
this.type = option.type;
v8_util.setHiddenValue(this, 'items', []);
nw.allocateObject(this, option);
}
which calls methods of the nw object, which is an object that is not available outside of this function, (i.e. the function acts as a closure over it.)
Further inspection of the various prototype methods of nw.gui.Menu shows that each call refers (internally) to this nw object to handle method dispatch to internally defined functions (written in C++).
So, rather than a group of standard JavaScript prototypical objects, the nw.gui module calls internal binary functions within the node-webkit runtime which are not exposed via its defined API.
UPDATE
From the node-webkit wiki:
Do not change UI types' prototype.