Posts tagged node.js
Dynamic dependencies in Node.js and Ember
0While working on a bower component for an Ember app, I ran into an issue trying to set up a dynamic hierarchical dependency setup. As the service I was building is to be added as a dependency to several other projects, there may be any number of modules up the tree that also need to be included into the service’s scope. While trying to work towards a solution, I came across an annoying limitation in the ES6 spec – you cannot easily import variable modules.
// Doesn't work import { default as widgets } from '*/widgets/*'; |
My solution to this problem involved a bunch of digging into how Node implements the new ES6 spec.
The solution actually ended up being a not-terribly-complex one, which is surprising after the trouble I had trying to work to a solution. As an example, let us assume that you are working with a component, trying to import a group of dynamically generated modules from the global scope, say with a pattern of “*/services/*-widget”. Let us also assume that the second wildcard is named after the setting application – adding distinction to the specific widget within the require entries’ scope, and ensuring widgets aren’t overwritten. Finally, let us assume that there are three separate applications at play here, we will call them base, consumer, and public, with “public” being the parent application consuming “consumer”, which in turn consumes “base”.
Setting up the main widget, first I set up the object methods. Rather than setting the whole thing to “export default {…}” and calling it a day, I just define it to the variable “widgetReturn”.
// base/app/services/base-widget.js // require entry: public/services/base-widget var widgetReturn = { sample: "Base Sample", betterSample: "Base Better Sample" }; |
For the sake of my sanity, I decided to take a regular expression approach rather than a much-more-involved manual check. The first regular expression will look for all of our widgets, the second will find the primary one. Both ignores the anything ending in “jshint”.
var widgetRegex = new RegExp("services\/(.+\w)-widget(?!\.jshint)", "g"); var precisionRegex = new RegExp("(.+)\/services\/(.+\w)-widget(?!\.jshint)", "g"); |
Next, we run through the list of required entries, trying to find matches, placing them either at the top of an array if it isn’t the primary entry, or the bottom if it is. This ensures that, while iterating through the resulting array, we include items in a more proper order.
var imports = []; // create an array of imported modules for (var entry in window.require.entries) { // iterate through module entries if (entry.match(localeRegex)) { // does the entry match our first pattern? // grab an array of matches based on our second pattern var includeCheck = precisionRegex.exec(entry); // verify that the second pattern has a length of three: the module name, // and the two wildcard matches. if (includeCheck !== null && includeCheck.length >= 3) { // if the two wildcards match, this is the primary entry if (includeCheck[1] === includeCheck[2]) { var include = Ember.RSVP.Promise.resolve(window.require(entry)); imports.push(include); } else { // not primary var include = Ember.RSVP.Promise.resolve(window.require(entry)); imports.unshift(include); } } else { // not primary var include = Ember.RSVP.Promise.resolve(window.require(entry)); imports.unshift(include); } } } |
Finally, we iterate through the array of imported modules, appending or update the results of the base widget object. Once we’ve finished, we export the freshly-updated widgetReturn object.
for (var i=0; i<imports.length; i++) { var impwidget = imports[i]["_result"].default; for (var attrName in impwidget) { widgetReturn[attrName] = impwidget[attrName]; } } export default widgetReturn; |
Now, when your consuming applications define widgets of their own, they just need to export the object, and the service above will handle the rest of the work.
// consumer/app/services/consumer-widget.js // require entry: public/services/consumer-widget export default { sample: "Consumer Sample", notQuiteAsGood: "Consumer Not Quite As Good" }; // app/services/public-widget.js // require entry: public/services/public-widget export default { sample: "Public Sample", awesome: "Public Awesome" }; |
The resulting object being generated from an include of “public/services/base-widget” will return the following. Combining the elements from each of the widget files.
include { default as widgets } from "public/services/base-widget"; Widgets:{ sample: "Public Sample", betterSample: "Base Better Sample", awesome: "Public Awesome", notQuiteAsGood: "Consumer Not Quite As Good" }; |