Dojo FAQ: What is the map config option?

By on July 3, 2013 8:08 am

In our previous post, we briefly highlighted the distinctions between packages, paths, and aliases. In particular, we noted that you can do some interesting things with paths and aliases for creating shortcuts of a sort, but one of the difficulties is that these shortcuts are global. What if I only want to override a module path, like we would with aliases, but I only want to do that in the context of one specific module? What if I want to be able to have two different modules load app/Widget but have them receive two different versions of the module? We can do that, using map.

First, it’s important to note that this only applies to newer versions of Dojo. If you’re using a newer release of Dojo 1.8 or any of the Dojo 1.9 releases, the map configuration option is available. The value is an object whose keys are the modules to be impacted, and the values of those keys are module IDs to be remapped. It’s a bit verbose in English, so let’s show it in some code.

	map: {
		"app/ModuleA": {
			"app/Widget": "app/DebugWidget"
		}
	}

At its most basic, this is a simple use of map. In this case, if app/ModuleA requests to load app/Widget, it’s actually going to transparently receive the app/DebugWidget module instead. Only app/ModuleA is affected in this case. All other requests for app/Widget (even in app/DebugWidget) will receive the original module.

You may also have a scenario where you have two concurrent versions of modules, and you want to ensure that one module receives a newer version, while a second module receives the older version. You can do this fairly easily with map:

	map: {
		"app/ModuleA": {
			"app/Widget": "app/WidgetOld"
		},
		"app/ModuleB": {
			"app/Widget": "app/WidgetNew"
		}
	}

In this scenario, app/ModuleA will get the old widget while app/ModuleB receives the new widget. Any other requests will just load app/Widget.

In the event that you wish to create a mapping that applies for all modules, map has you covered there, as well. There’s a special "*" module value supported that applies the mapping to all modules. If a more specific module mapping exists, that one will take precedence, so you can safely use "*" and not have to worry about getting the wrong module. Taking our previous example, perhaps we want to ensure that everyone receives the old module, and only app/ModuleB gets the new one. We could extend the previous map like so:

	map: {
		"*": {
			"app/Widget": "app/WidgetOld"
		}
		"app/ModuleA": {
			"app/Widget": "app/WidgetOld"
		},
		"app/ModuleB": {
			"app/Widget": "app/WidgetNew"
		}
	}

You’ll notice that I’ve left app/ModuleA in the mapping, even though it’s redundant with the "*" mapping. Perhaps app/ModuleA has some dependencies on less-than-ideal behaviors in the older widget and it’s stuck using the old one until we’ve had a chance to update app/ModuleA appropriately, but everyone else is ready to move up. That would mean we could make one change to our map, and now everyone would pick up the newer module until we’d had a chance to fix up app/ModuleA:

	map: {
		"*": {
			"app/Widget": "app/WidgetNew"
		}
		"app/ModuleA": {
			"app/Widget": "app/WidgetOld"
		},
		"app/ModuleB": {
			"app/Widget": "app/WidgetNew"
		}
	}

Then, once we’d had a chance to update everyone who used app/Widget, we could clean up and get rid of the excess modules and mappings.

However, this approach is not limited to just remapping specific module IDs. For each of the keys, it’s doing a partial mapping from left to right against module IDs, and so you don’t have to specify individual modules, but can simply list a partial ID, and it will map those out as appropriate, with the longest path taking precedence.

For example, you may wish to do something like use the 1.7 version of Dijit for a specific section of your application, all living under a app/widget folder. You could configure your application like this:

	map: {
		"app/widget": {
			"dijit": "dijit1.7"
		}
	}

Assuming you have dijit1.7 set up as a package, this would ensure that any module within the app/widget directory would receive Dijit 1.7 widgets instead of the current copy of Dijit. Therefore, app/widget/Form might request dijit/form/Form to use as a base, but it will receive the module loaded from dijit1.7/form/Form instead, even though app/widget/Form was not explicitly specified. So long as you’re using the newer version of Dojo as your loader, this will work well. You could even remap entire packages this way:

	map: {
		"package1": {
			"dijit": "dijit1.7"
		},
		"package2": {
			"dijit", "dijit1.8"
		}
	}

The map config option is very powerful, and it currently meets the specification as laid out in the common config section of the amdjs-api wiki. This configuration is incredibly powerful and useful, and we hope that you get some value out of better understanding it!

Comments

  • Corey Alix

    But no way of doing a wild card substitution where “vendor” -> “acme” regardless of the mid? I’d need to setup a mapping for every “vendor” folder?

  • Maps start from the beginning of the module ID and split by /, so no, there is no arbitrary wildcard mapping function. You probably need to reconsider your application’s directory structure if you have a requirement to set up so many mappings that it becomes burdensome.

  • Joe

    Hi Brian, great post. I am having an issue using the dojoConfig.map when using a built version of dojo. Any tips or tricks on getting this working? Works great when I run against dojo source. I posted full details of my question on stackoverflow:

    http://stackoverflow.com/questions/26912994/dojoconfig-map-while-running-against-a-cdn-built-source

  • Michael Woodward

    Is it possible to do something like this? It appears as if things are just in a circular loop (required modules are just empty objects)

    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’
    },
    }

  • I think you probably want something like ‘moment/*’ for your last entry?

  • Michael Woodward

    do paths work with the map object, or are there some compatibility issues since map is newer to dojo loader?

  • They are somewhat redundant, and map is what you should use here. Generally a path is still fine for defining the path of a package, but shouldn’t be used to remap a dependency.

  • Michael Woodward

    Thanks for your help. As it turns out, the dojo loader wasn’t playing nicely with the MomentJS locale files… there is something strange going on with either moments UMD/AMD require/define statements or with dojo’s implementation of an AMD loader.

  • Michael Woodward

    I also seem to be having issues with dojo ignoring the map configuration after dojo is built/compressed. Does the dojo resolver work differently in that case? It seems to be ignoring the map I have set up and just treating it as an absolutely pathed module, instead of resolving the path.

  • Moment just joined the JS Foundation, so I’ll see what I can find out. Do you want to open an issue against moment or against Dojo for this and then I’ll chase it down?

  • Melkor Baughlir

    It seems that, using star mapping, the original mapped files are removed from the build results. This behaviour was not present in 1.8.3, at least. But I did not found any documentation about this.

  • It shouldn’t be. You’re the first to report a regression though it doesn’t mean you’re not correct, just that I’ve not seen this issue yet. Could you please post a config and which version of Dojo you’re using here or to the dojo-interest mailing list?

  • Melkor Baughlir

    We have upgraded from Dojo 1.8.3 to Dojo 1.11.2. The map was used in the build config an in the dojo config, at runtime.
    …..
    map: {
    “*”: {
    “dojo/date/locale”: “team/patch/dojo-date-locale”
    }
    }
    ….
    The problem arose when another application was using the built layers but forgotting to add a map declaration in their dojo config: so the original locale.js from Dojo was requested. But it wasn’t there.

  • Meaning dojo/date/locale wasn’t in the original built layers?

  • Melkor Baughlir

    Exactly.

  • Ok, we’ll look into it… I guess if it had been 1.12.x+ I might be less surprised, but we’ll see if we can reproduce.

  • Melkor Baughlir

    I have tried with a ‘named’ mapping and the original file was not deleted. Something like:
    …..
    map: {
    “team/app”: {
    “dojo/date/locale”: “team/patch/dojo-date-locale”
    }
    }
    ….
    So this made me think was the star mapping configuration.