RequireJS with MomentJS Locales #1554

Closed
maggiepint opened this Issue Jun 15, 2016 · 3 comments

Projects

None yet

3 participants

@maggiepint

Hi,
I'm one of Moment.js's maintainers. We have several outstanding issues related to using moment with Require.js that we just aren't able to help with since none of us have in-depth knowledge of the tool. Is there any chance we could get some advice on how to get things working, and whether moment is in fact correctly set up to work with Require?

The issues are as follows:

Someone who is unable find a good way to bootstrap our locale files with require: moment/moment#3243

Someone having trouble with exporting moment global: moment/moment#2831

A pull request to change over to a relative AMD path to make things work better with require: moment/moment#3082

Other old ancient issues: moment/moment#2574 moment/moment#2466

Could you advise if any changes need to be made to our code to make things work properly, and if so how those changes might affect other tools (System.js etc)?

Also, does this documentation need to be updated?
http://momentjs.com/docs/#/use-it/require-js/

I'm happy to update documentation if you advise me on how to do so. I think that how to load locales with require is probably something that needs to be added.

Thank you so much in advance. I will help you in any way you need with this. We really want moment to work well with Require given Require's popularity. We are just unable to keep up with proper use of all of the varying tools the JS community has created.

@jrburke
Member
jrburke commented Jun 15, 2016

Here is a shot at answering the questions. I decided to consolidate them here instead of responding to the individual moment issues, since the answers build on each other.

What makes up the moment package

Looking at the bower and npm installs, it seems to be a directory with a "main" module, "moment", and some support modules in the locale/ directory. Those locale modules depend on the "moment" that is one directory level up from it.

Given that, people using requirejs should be encouraged to treat moment as a package, installed in their project with that hierarchy. I think this will also translate well to the future when an ES module loader API is solidified.

Basic config

requirejs users should be encouraged to install the package via bower or npm, then either use a tool like adapt-pkg-main to create adapter "main" modules, or if doing it manually, use packages config:

requirejs.config({
  packages: [{
    name: 'moment',
    // This location is relative to baseUrl. Choose bower_components
    // or node_modules, depending on how moment was installed.
    location: '[bower_components|node_modules]/moment'
    main: 'moment'
  }]
});

With this setup, then moment/moment#3082 should be applied, so that the dependency in the locales is '../moment'. This matches the CJS path, so a good indication that it is a good way to keep it matched. Although, do not use '../moment.js', using '.js' in requirejs indicates a URL and not a module ID that should be resolved relatively to other module IDs.

Referring to locales

This is in relation to moment/moment#2466: to refer to a locale with the above configuration set up, a locale module is referenced via an ID like moment/locale/de.

Loading a locale dynamically

This is reference to moment/moment#2574, wanting to dynamically load the locale later, and configure moment to use it, and how to know when moment an be used after the locale is loaded:

define(function(require) {
  // Inside some module after the locale is detected. This is the 
  // case where the locale is not known before module load time.
  require(['moment/locale/de'], function(localeModule) {
    moment.locale('de');
    // Use moment now that the locale has been properly set.
  });

Want to just refer to 'moment' and have the locale also loaded.

If the locale is known ahead of time, and if, as in moment/moment#3243, they just want 'moment' to refer to moment with the locale loaded, they can use map config to make it appear like 'moment' + locale is just 'moment':

// Main config call.
requirejs.config({
    map: {
        // For all modules, if they ask for 'moment', use 'moment-adapter'
        '*': {
            'moment': 'moment-adapter'
        },
        // However, for moment-adapter, and moment/ modules, give them the
        // real 'moment*' modules.
        'moment-adapter': {
            'moment': 'moment'
        },
        'moment': {
            'moment': 'moment'
        },
    }
});

// Detect what locale to use and set this variable.
var locale = '';

// Use a second config call to set the dynamic path.
// This allows the above config to be use in an r.js
// build, which will only look for the first requirejs.config
// call and which does not support arbitrary execution of
// code that is needed here to determine the locale.
// For the build, put in a paths or map config in the r.js
// build config to point 'moment/calculatedLocale' to the
// locale module you want bundled in a build, or set the paths
// config to be 'moment/calculatedLocale': 'empty:' to just
// skip it for the build and dynamically load it at runtime.
requirejs.config({
    map: {
        'moment-adapter': {
            // Points the calculatedLocale to the dynamically determined ID.
            'moment/calculatedLocale': 'moment/locale/' + locale
        }
    }
});

// Define this module inline after the two requirejs calls. Or, it could be defined
// in its own file, an just make it an anonymous define in that case, define(['moment... 
define('moment-adapter', ['moment', 'moment/calculatedLocale'], function(moment) {
    moment.locale('de');
    return moment;
});

Wanting a global

I believe moment/moment#1220 is fine, not exporting a global by default. Going forward with ES modules, this will be the natural state for modules anyway. But that is a project call on if they want to support a noGlobal: false config to allow a global.

If there was not a config to set a global for moment, if someone wanted to create a global, as in moment/moment#2831, it could use the 'moment-adapter' solution above with a map config, but it would set the global in its module body:

define('moment-adapter', ['moment', 'moment/calculatedLocale'], function(moment) {
    moment.locale('de');

    // Set the global.
    window.moment = moment;

    return moment;
});

Summary

The concerns in requirejs/AMD modules around how to do module referencing and package layout will be very similar in an ES module world. The API may be different once the ES module loader APIs are finally defined, but I expect the same concepts to be expressed there too. So the concept transfer should be applicable.

I cannot speak to SystemJS, but in its current form, it is able to load AMD and CJS modules, I would think it would have something that could read the requirejs configs as expressed above, or have close equivalents.


Closing as a discussion ticket, but feel free to continue discussion here.

@jrburke jrburke closed this Jun 15, 2016
@maggiepint

Thank you so much for this! I'm going to try to close out some tickets today and update docs. I might have more questions as I go but you've been very helpful.

@chapterjason

I create a module configuration to set the locale once via requirejs. Easier to keep all moments in the same language on the page.

Configuration

requirejs.config({
	config: {
		'moment-adapter': {
			locale: 'de-DE'  // inject here with PHP or your rendering engine once your language
		}
	}
});

Adapter

define('moment-adapter', ['moment', 'module'], function (moment, module) {
	var config = module.config();
	moment.locale(config.locale || 'en-US');
	return moment;
});

Use
import * as moment from 'moment-adapter';

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment