In my ASP.NET MVC project I simply inserted <script></script> into my views - it caused code duplications and spaghetti code.
Now, I have decided to switch to modular JS and have implemented the following mechanism:
- ALL actions are only executed through the framework and its modules - whether it is a view or object behavior, date-to-string converter or websocket connection
- every module exposes
exportsfunctions, which can be called by anybody, including other modules - all functions including
exportsandinitare called within a sandboxedcontext, which includes both objects specific for the module and objects, injected by a framework (currently, I am implementing the mechanism to "hide" / disallow non-context methods within a module)
Framework object with the only register function:
window.kibfw = {
register: function(name, module) {
if (!name || !module.init)
throw new Error('Could not load a module ' + name);
if (name === 'register')
throw new Error('Module cannot have name "register" which is reserved');
if (this[name])
throw new Error('Module ' + name + ' is already registered.');
module.context = module.context || {};
/* Here, inject the required context objects (jQuery, libraries etc.) */
module.context.$ = window.jQuery;
module.context.kibfw = window.kibfw;
try {
module.init.apply(module.context, arguments);
} catch(e) {
throw new Error('Error during module initialization: ' + e.message + '\n' + e.stackTrace);
}
var moduleExports = (this[name] = {});
for (var exportFnName in module.exports) {
if (!module.exports.hasOwnProperty(exportFnName))
continue;
moduleExports[exportFnName] = (function (exportFn) {
return function() {
try {
exportFn.apply(module.context, arguments);
} catch (e) {
throw new Error('Module: ' + name + '. Error during method invocation: ' + e.message + '\n' + e.stackTrace);
}
};
})(module.exports[exportFnName]);
}
}
};
Example module:
kibfw.register('decorator', {
init: function () {
this.$decorateItems = this.$('input');
},
context: {
$decorateItems: undefined
},
exports: {
setColor: function (color) {
this.$decorateItems.css('color', color);
}
}
});
// Usage:
$("button").click(function() {
kibfw.decorator.setColor($(this).attr('data-color'));
});
How do you think, would it be useful and scalable?
Which problems can I stumble upon while using this approach?
-
2\$\begingroup\$ Question - why not use AMD or a preprocessor like Webpack to achieve this? You're reinventing CommonJS here. \$\endgroup\$Dan– Dan2016年07月29日 12:16:13 +00:00Commented Jul 29, 2016 at 12:16
2 Answers 2
Very readable code, some minor comments:
- Some folks dislike the lacking curly braces for one liners, I dont mind it here myself
- JsHint will complain about you creating a function in a
forloop, I think you should indeed refactor that - Using
this[name]to register modules would be to fickle for me (shenanigans withbind,apply, andcallcome to mind), I would prefer a self executing function with avarstatement. - You only have 1 line of comment, for a very straightforward piece of code, I would have commented the
moduleExports[exportFnName] = (function (exportFn) {line - I would split this out in to 2 different error messages:
if (!name || !module.init) throw new Error('Could not load a module ' + name); - Actually, in your code I see that
initis a bit contrived, I would not make that mandatory personally
Now, when I take a step back, I think you might be suffering from Second System Syndrome. Consider that in effect you are replacing
$("button").click(function() {
$('input').attr('data-color'));
});
with
kibfw.register('decorator', {
init: function () {
this.$decorateItems = this.$('input');
},
context: {
$decorateItems: undefined
},
exports: {
setColor: function (color) {
this.$decorateItems.css('color', color);
}
}
});
// Usage:
$("button").click(function() {
kibfw.decorator.setColor($(this).attr('data-color'));
});
I like window.kibfw, it is pretty beautiful. But I would hate to have to write a website with it.
-
\$\begingroup\$ Thanks for your response! Very useful answer, I will accept this one. I have come up with a simple solution, which I will show in another answer. \$\endgroup\$Yeldar Kurmangaliyev– Yeldar Kurmangaliyev2016年08月01日 04:53:13 +00:00Commented Aug 1, 2016 at 4:53
After konijn's feedback, I have come up with a simpler solution
Module framework:
var Module = (function() {
function module(name, constructor) {
if (module[name]) {
throw new Error('Module ' + name + ' already exists.');
}
var exports = (module[name] = {});
constructor.call(null, exports);
}
return module;
})();
New module declaration (new keyword doesn't have any practical usage, just for readbility):
new Module('decorator', function(exports) {
var $decorateItems = $("p");
exports.setColor = function(color) {
$decorateItems.css('color', color);
};
});
Calling an exported function:
Module.decorator.setColor('blue');
After some code analysis, I have understood that all my modules are independent, and only a couple modules will be called from others - I will simply load them first using ASP.NET MVC Bundling.
Modules dependency and requirements features really seem like an overkill here.
You must log in to answer this question.
Explore related questions
See similar questions with these tags.