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
exports
functions, which can be called by anybody, including other modules - all functions including
exports
andinit
are 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
for
loop, I think you should indeed refactor that - Using
this[name]
to register modules would be to fickle for me (shenanigans withbind
,apply
, andcall
come to mind), I would prefer a self executing function with avar
statement. - 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
init
is 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.
Explore related questions
See similar questions with these tags.