Moment.js - "Accessing Moment through the global scope" warning message - javascript

I've recently downloaded last version of moment.js and it begins to show the following message when trying to call, for example, moment().add(1, 'day');
"Deprecation warning: Accessing Moment through the global scope is deprecated, and will be removed in an upcoming release."
Which is the best way to call moment methonds?
Update: Figured out the problem
The problem was present because I have requirejs in my project and momentjs was trying to warn me that I should use momentjs as a module dependency instead.
The following code was extracted from momentjs v2.9.0
// CommonJS module is defined
if (hasModule) {
module.exports = moment;
} else if (typeof define === 'function' && define.amd) {
define('moment', function (require, exports, module) {
if (module.config && module.config() && module.config().noGlobal === true) {
// release the global variable
globalScope.moment = oldGlobalMoment;
}
return moment;
});
makeGlobal(true);
} else {
makeGlobal();
}
//And this is the 'makeGlobal' function. globalScope
function makeGlobal(shouldDeprecate) {
/*global ender:false */
if (typeof ender !== 'undefined') {
return;
}
oldGlobalMoment = globalScope.moment;
if (shouldDeprecate) {
globalScope.moment = deprecate(
'Accessing Moment through the global scope is ' +
'deprecated, and will be removed in an upcoming ' +
'release.',
moment);
} else {
globalScope.moment = moment;
}
}
So, if I use this library in a CommonJS environment, then I should use import statement.
If I use requirejs, then I should include momentjs as a dependency of my modules.
Finally, if neither the other cases accomplish, then I can use it directly from global scope (window object in browser)

You can use requirejs to pull it in rather than using the global scope:
require.config({
paths: {
"moment": "path/to/moment",
}
});
define(["moment"], function (moment) {
moment().format();
});
Taken from http://momentjs.com/docs/

This really isn't an answer, but an appreciation of the problem:
There has yet to be a cogent explanation of why the deprecation occurs, or a newbie explanation of what it is. Specifically, not paragraph saying 'if you do it the old way, it breaks in a subtle way'. The closest is a bug report that, using node, a single symbol is defined in the global namespace (https://github.com/moment/moment/issues/1214), which is mostly philosophy.
The deprecation comes on usage, so its unclear to people why. It appears to need be fixed in installation.
No one on any chat node has explained it, outside of mirroring the require.js boilerplate. The comment seems to continue as "do it this way and it works". The boilerplate does not cover all users.
Some failing lines include simple constructors like moment(value), which is the whole point of the library.
It appears the minor version upgrade from moment 2.9.0 to 2.10.0 may have forced deprecated code to break, at least for those using ECMAScript and a fallback. Reverting to 2.9.0 will allow you to keep working for now. If you only had the related breakage of moment.duration.fn disappearing, you can upgrade to 2.10.1 or above.

Related

Rails 6 Webpacker calling javascript function from Rails view

I have following structure for Javascript in my Rails 6 app using Webpacker.
app/javascript
+ packs
- application.js
+ custom
- hello.js
Below shown is the content in the above mentioned JS files
app/javascript/custom/hello.js
export function greet(name) {
console.log("Hello, " + name);
}
app/javascript/packs/application.js
require("#rails/ujs").start()
require("jquery")
require("bootstrap")
import greet from '../custom/hello'
config/webpack/environment.js
const { environment } = require('#rails/webpacker')
const webpack = require('webpack')
environment.plugins.prepend('Provide',
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
Popper: ['popper.js', 'default']
})
)
module.exports = environment
Now in my Rails view I am trying to use the imported function greet like shown below
app/views/welcome/index.html.haml
- name = 'Jignesh'
:javascript
var name = "#{name}"
greet(name)
When I load the view I am seeing ReferenceError: greet is not defined error in browser's console.
I tried to search for a solution to this problem and found many resources on web but none turned out to help me. At last when I was drafting this question in the suggestions I found How to execute custom javascript functions in Rails 6 which indeed is close to my need however the solution shows a workaround but I am looking for a proper solution for the need because I have many views which needs to pass data from Rails view to JS functions to be moved custom files under app/javascript/custom folder.
Also I would highly appreciate if anybody can help me understand the cause behind the ReferenceError I am encountering.
Note:
I am not well-versed in Javascript development in Node realm and also new to Webpacker, Webpack, Javascript's modules, import, export, require syntax etc so please bear with me if you find anything silly in what I am asking. I have landed up in above situation while trying to upgrade an existing Rails app to use version 6.
Webpack does not make modules available to the global scope by default. That said, there are a few ways for you to pass information from Ruby to JavaScript outside of an AJAX request:
window.greet = function() { ... } and calling the function from the view as you have suggested is an option. I don't like have to code side effects in a lot of places so it's my least favorite.
You could look at using expose-loader. This would mean customizing your webpack config to "expose" selected functions from selected modules to the global scope. It could work well for a handful of cases but would get tedious for many use cases.
Export selected functions from your entrypoint(s) and configure webpack to package your bundle as a library. This is my favorite approach if you prefer to call global functions from the view. I've written about this approach specifically for Webpacker on my blog.
// app/javascript/packs/application.js
export * from '../myGlobalFunctions'
// config/webpack/environment.js
environment.config.merge({
output: {
// Makes exports from entry packs available to global scope, e.g.
// Packs.application.myFunction
library: ['Packs', '[name]'],
libraryTarget: 'var'
},
})
// app/views/welcome/index.html.haml
:javascript
Packs.application.greet("#{name}")
Take a different approach altogether and attach Ruby variables to a global object in your controller, such as with the gon gem. Assuming you setup the gem per the instructions, the gon object would be available both as Ruby object which you can mutate server-side and in your JavaScript code as a global variable to read from. You might need to come up with some other way to selectively call the greet function, such as with a DOM query for a particular selector that's only rendered on the given page or for a given url.
# welcome_controller.rb
def index
gon.name = 'My name'
end
// app/javascript/someInitializer.js
window.addEventListener('DOMContentLoaded', function() {
if (window.location.match(/posts/)) {
greet(window.gon.name)
}
})
#rossta Thanks a lot for your elaborate answer. It definitely should be hihghly helpful to the viewers of this post.
Your 1st suggestion I found while searching for solution to my problem and I did referenced it in my question. Like you I also don't like it because it is sort of a workaround.
Your 2nd and 3rd suggestions, honestly speaking went top of my head perhaps because I am novice to the concepts of Webpack.
Your 4th approach sounds more practical to me and as a matter of fact, after posting my question yesterday, along similar lines I tried out something and which did worked. I am sharing the solution below for reference
app/javascript/custom/hello.js
function greet(name) {
console.log("Hello, " + name)
}
export { greet }
app/javascript/packs/application.js
require("#rails/ujs").start()
require("bootstrap")
Note that in above file I removed require("jquery"). That's because it has already been made globally available in /config/webpack/environment.js through ProvidePlugin (please refer the code in my question). Thus requiring them in this file is not needed. I found this out while going through
"Option 4: Adding Javascript to environment.js" in http://blog.blackninjadojo.com/ruby/rails/2019/03/01/webpack-webpacker-and-modules-oh-my-how-to-add-javascript-to-ruby-on-rails.html
app/views/welcome/index.html.haml
- first_name = 'Jignesh'
- last_name = 'Gohel'
= hidden_field_tag('name', nil, "data": { firstName: first_name, lastName: last_name }.to_json)
Note: The idea for "data" attribute got from https://github.com/rails/webpacker/blob/master/docs/props.md
app/javascript/custom/welcome_page.js
import { greet } from './hello'
function nameField() {
return $('#name')
}
function greetUser() {
var nameData = nameField().attr('data')
//console.log(nameData)
//console.log(typeof(nameData))
var nameJson = $.parseJSON(nameData)
var name = nameJson.firstName + nameJson.lastName
greet(name)
}
export { greetUser }
app/javascript/packs/welcome.js
import { greetUser } from '../custom/welcome_page'
greetUser()
Note: The idea for a separate pack I found while going through https://blog.capsens.eu/how-to-write-javascript-in-rails-6-webpacker-yarn-and-sprockets-cdf990387463
under section "Do not try to use Webpack as you would use Sprockets!" (quoting the paragraph for quick view)
So how would you make a button trigger a JS action? From a pack, you add a behavior to an HTML element. You can do that using vanilla JS, JQuery, StimulusJS, you name it.
Also the information in https://prathamesh.tech/2019/09/24/mastering-packs-in-webpacker/ helped in guiding me to solve my problem.
Then updated app/views/welcome/index.html.haml by adding following at the bottom
= javascript_pack_tag("welcome")
Finally reloaded the page and the webpacker compiled all the packs and I could see the greeting in console with the name in the view.
I hope this helps someone having a similar need like mine.

Testing JavaScript without exports using the Jest testing framework

I am in the process of moving an old JS codebase to modern JS. First step though is to add some tests to the current codebase. The current code base is essentially a bunch of individual JS files each wrapped in an IIFE.
This in itself is a problem for tests because, unless something is exposed to the global object, you cannot reach into the IIFE. Some of the code I am refactoring to be simple JS object with properties, which is attached to a namespace(namespace below is just a placeholder name) on the global object, for example:
var namespace = window.namespace || {};
var paymentsHandlerUtils = {
getNewValue: function(selectedAmount) {
'use strict';
return selectedAmount < 1 || isNaN(selectedAmount)
? ''
: '$' + selectedAmount;
},
getSelectedAmount: function(value) {
'use strict';
return value % 1 === 0 ? parseInt(value) : parseFloat(value).toFixed(2);
}
};
namespace.paymentsHandlerUtils = paymentsHandlerUtils;
My question is, how would you go about testing this with Jest? I have tried requiring the above as follows:
const paymentsHandlerUtils = require('../js/components/payments/payments-handler-utils.js');
This runs, but the paymentsHandlerUtils object is just an empty {}. Not surprising, as nothing is being returned by simply executing the JS. However, window.namespace is also undefined. Seems like the code is not being executed in the context of jsDOM, so the global(s) is not created.
Is there a way to get this to work, or is this simply not a use case for Jest? Thanks in advance.
I don’t think there is a way to access module globals when imported as it goes against the principle of encapsulated modules in the first place.
An alternative which would require the least refactoring would be to add the following code to all your modules:
if (typeof exports === "object") {
module.exports = paymentsHandlerUtils;
}
namespace.paymentsHandlerUtils = paymentsHandlerUtils;
It is inspired by the old UMD (Universal Module Definition). The condition detects if you are running in a commonjs environment and exports your variable. You will then be able to require it in your tests:
const paymentsHandlerUtils = require('../js/components/payments/payments-handler-utils.js');
Otherwise you need to use another test suite that works in the browser, because Jest doesn’t.
Good luck to your migrations!
In fact this is now possible with rewire. Here's what you have to do:
install rewire (npm i rewire)
in your jest file,
const rewire = require("'rewire');
const paymentsHandlerUtilsRewire = rewire('../js/components/payments/payments-handler-utils.js');
const namespace = paymentsHandlerUtilsRewire.__get__('namespace');
do what you were planning to do with namespace and namespace.paymentsHandlerUtils
Read more in the rewire repo; there's also babel-plugin-rewire for ES6+ (see, for instance, here).

Include JavaScript code without packaging as a module

How do I require a JavaScript library that is not packaged as a UMD-compatible module (AMD, CommonJS) with webpack?
I don't want the library to pass through a loader. I just want it to be included in a <script> tag whenever it is required, and for webpack to manage this dependency.
I don't want to simply place it in a script tag in my index.html, because I want to take advantage of webpack's code-splitting, and only include it when necessary.
I've read about 'externals', I'm not sure if this has anything to do with it. The docs are not clear enough.
Thanks :)
Updated question
Also this question is specifically about front-end libraries that only need to be included via a <script> tag to work.
You can add amd support to your library and then load it with webpack.
Some links that can help you achieve are:
http://ifandelse.com/its-not-hard-making-your-library-support-amd-and-commonjs/
https://github.com/mbostock/d3/pull/1921
What is basically being done is that at the top of your library you can check whether it's a commonjs environment or an AMD environment. Accordingly you can export your library.
One example can be this ( taken from 1st link )
(function (root, factory) {
if(typeof define === "function" && define.amd) {
// Now we're wrapping the factory and assigning the return
// value to the root (window) and returning it as well to
// the AMD loader.
define(["postal"], function(postal){
return (root.myModule = factory(postal));
});
} else if(typeof module === "object" && module.exports) {
// I've not encountered a need for this yet, since I haven't
// run into a scenario where plain modules depend on CommonJS
// *and* I happen to be loading in a CJS browser environment
// but I'm including it for the sake of being thorough
module.exports = (root.myModule = factory(require("postal")));
} else {
root.myModule = factory(root.postal);
}
}(this, function(postal) {
// module code here....
return myModule;
}));

Trying to use a non-AMD module in require

I'm trying to use a non-AMD JS module I have (called mymodule) in requirejs. This is the first time to try to do so. Following what I saw from bootstrap-amd, and underscore-amd, I tried the following:
// start of mymodule
if(typeof mymodule === "undefined") loaded = {};
mymodule.cache = (function() {
...
return {
...
};
})();
mymodule.main = (function() {
...
return {
doSomethingJQuery: function() { ... }
}
})();
.
.
.
// end of mymodule
// This part I added for the sake of requirejs
if(typeof define === "function" && define.amd) {
define(function() {
return mymodule;
});
}
You can see the first part is my module (using, I hope, good JS module pattern :). I wanted to keep the option of using the module independent from require JS so if I didn't want to use requirejs in another project, I don't have to. So I kinda didn't want to wrap the entire module in a define(...) function... unless it's common practise to have a mymodule.js and mymodule-amd.js files.
Anyway my module loads,
define(["mymodule"], function (mymodule) {
// do it - ReferenceError: jQuery is not defined :(
mymodule.doSomethingJQuery();
.
.
.
..but as doSomethingJQuery() depends on jQuery, I get an error. How can I bring in jQuery here, or should it be done differently? I tried to find some examples but pretty new to requirejs. Would like to do things in the proper way. As mentioned, I'd like to also keep the option to use the module independently. Would appreciate any pointers here, thanks.
You need to make your module dependent on jQuery and pass the reference to a function that will define your module. Change the code that defines your module to:
if(typeof define === "function" && define.amd) {
define(['jquery'], factory);
}
else
mymodule = factory(jQuery);
The else part is necessary for cases that are not AMD because you're now using a factory function. (You could also have a branch to deal with CommonJS environments like Node but I'm not going to add it here.)
Then make a function that defines your module:
function factory($) {
// define the module
return mymodule;
}
The entire thing could be wrapped in an IIFE to avoid polluting the global space with factory.

What does it mean to "export to the browser"

The backbone.js annotated source describes the following piece of code
var Backbone;
if (typeof exports !== 'undefined') {
Backbone = exports;
} else {
Backbone = root.Backbone = {};
}
as "The top-level namespace. All public Backbone classes and modules will be attached to this. Exported for both CommonJS and the browser."
What does "exported for the browser" mean in this context?
In CommonJS, your modules are sequestered and anything you want to share with the thing that requires you is shared through the "exports" variable. Node.js, for instance, uses this.
On the other hand, if you are just in the browser, then you don't use the exports variable and you add a new variable in root which ultimately points to the window global var.
In other words, if we are using something that supports CommonJS, export Backbone. If not, put it in the root context instead.

Categories