How to modularize a legacy JavaScript project - javascript

I need to use webpack to build a legacy JS project that so far did not have a
build system.
The project is split up into ~30 JS files, all of which assign functions and
fields to a single global myApp mega-object.
Previously, all these files were included separately in one namespace. It looked
somewhat like this:
myApp.js:
const myApp = {
saySomething: function(x) {
console.log(x);
},
something: "something"
};
someModule.js:
myApp.anotherModule = {
foo: function(x) {
myApp.someModule.bar(x);
}
};
anotherModule.js:
myApp.someModule = {
bar: function(x) {
myApp.saySomething(x);
},
start: function() {
myApp.someModule.foo(myApp.something);
}
};
The entrypoint would call myApp.someModule.start(), the control flow would
weave between the different parts of the mega-object.
I tried factoring out an index.js like so:
const myApp = require('./myApp');
myApp.someModule = require('./someModule');
myApp.anotherModule = require('./anotherModule');
(with the appropriate module.exports declarations in the respective files.)
But when e.g. anotherModule's start function calls myApp.someModule.foo(),
that's not in scope. I can't bring it into scope with require in the module
itself — I'd have to include someModule, which would in turn have to include
anotherModule, etc.
Is there a way out of this mess without having to refactor the entire project
(and utterly break the test suite, etc.?)
In other words: can I use webpack to assemble a mega-object and not isolate its parts' respective scope?

you should pass a myApp reference to the require
require('./someModule')(myApp);
and the module should export a function accepting myApp as parameter
myApp.anotherModule = function(myApp) {
return {
foo: function(x) {
myApp.someModule.bar(x);
}
}
};
so
myApp.someModule = require('./someModule')(myApp);
executes the function and returns your object with functions binded to myApp

Related

Best practice to inject modules in angularJS using browserify

I'm trying to simplify my angular application with gulp and browserify. Unfortunately I'm struggling with the concept of require and exports mixed with angulars dependency injection (DI). I have to change a lot of files in order to get the browserify running so I want ro get some things clear before I start.
For example, I have a module moduleAB that contains two factories, each defining a class. FactoryA creates classA and factoryB creates classB. classB inherits from classA. So in my code I have something like:
angular.module("moduleAB", [])
.factory("factoryA", ["$window", function(wndw) {
return function classA (a, b) {
this.a = a;
this.b = b;
};
}])
.factory("factoryB", ["factoryA", function(classA) {
var classB = function (a, b) {
classA.call(this, a, b);
};
classB.prototype = Object.create(classA.prototype);
classB.prototype.constructor = classB;
return classB;
}]);
Now I read that for perfect usage of browserify I have to split my modules into separate files and use an index.js to require the parts of this module:
var angular = require("angular");
module.exports = angular.module("moduleAB", [])
.factory("factoryA", require("./factoryA"))
.factory("factoryB", require("./factoryB"));
So far, so good. Now I need files for factory A and B. But what's the best way to implement these and preserve DI? Should I write something like (factoryA.js):
module.exports = ["$window", function(wndw) {
return function classA (a, b) {
//...
};
}];
And (factoryB.js):
module.exports = ["factoryA", function(factoryA) {
//...
}];
Or is it better to do (factoryB.js):
var factoryA = require("./factoryA");
module.exports = [function() {
//...
}];
Or (factoryB.js):
module.exports = [require("./factoryA"), function(factoryA) {
//...
}];
It's important that after minification DI still works :)

How to Augument commonjs module from requiring code?

I am wondering how I can augument a commonjs module from another module that requires it.
Let's assume that I have three files, two commonjs modules as below:
my-example-module.js
function MyExampleModule(){}
MyExampleModule.prototype = {
bindings: {
some: 'random',
prop: 'values'
}
}
module.exports = MyExampleModule;
another-example-module.js
var MyExampleModule = require('./my-example-module');
function AnotherExampleModule(){}
AnotherExampleModule.prototype = {
getMyExampleModuleBindings: function(){
var module = new MyExampleModule();
return module.bindings;
}
}
module.exports = AnotherExampleModule;
app.js
var MyExampleModule = require('./my-example-module');
var AnotherExampleModule = require('./another-example-module');
//modify?!?
var anotherExampleModule = new AnotherExampleModule();
console.log(anotherExampleModule.getMyExampleModuleBindings());
So what I want to do is have //modify?!? be some kind of code that will alter the original MyExampleModule prototype so when anything else attempts to require MyExampleModule it will get the modified version.
A concrete question would be - what should I replace //modify?!? with so I get logged out the the console with the assumption that my-example-module.js is read only.
{
some: 'random',
prop: 'values',
added: 'binding'
}
If you want to do this in nodejs/iojs, this is pretty simple. When node imports a CommonJS module (let's call it A), it creates an internal object.
When another module (B) wants to load the same module (A), it just gets the reference to that internal object. So if you change something on MyExampleModule in app.js it is also applied to the MyExampleModule in another-example-module.js:
app.js
var MyExampleModule = require('./my-example-module');
var AnotherExampleModule = require('./another-example-module');
//modify:
MyExampleModule.prototype.bindings = {
some: 'random',
prop: 'values',
added: 'binding'
};
var anotherExampleModule = new AnotherExampleModule();
console.log(anotherExampleModule.getMyExampleModuleBindings());
Since you create a new instance of MyExampleModule in another-example-module.js after you call MyExampleModule.prototype.bindings = {...} in app.js, the new instance will already be created with the modified .prototype.
While I haven't tested this in browserify, it certainly works in webpacks implementation of CommonJS as well.
Check out the working example on runnable (app.js is called server.js):
http://code.runnable.com/VZPdN5k65gE5vUIz

Requirejs dynamic reference to sub module methods?

Using requires, I’ve split larger class structures down into modules that use other modules within directory. There’s a main file that instantiates the other sub modules. This is an API class with two modules. One that deals with posting data to the endpoint, and the other that holds functions that are helpers to that post module:
define([
'./Post',
'./Helper'
], function (PostModule, HelperModule) {
'use strict';
var module = function () {
this.Post = new PostModule();
this.Helper = new HelperModule();
};
return module;
});
Now I can chain these modules like this:
var foo = new ApiModule();
foo.Post.postToAPI();
foo.Helper.matchAPIactionToEvent();
which is exactly what I want..
BUT, the problem is within the Post.js file, is that it doesn’t know anything about the Helper.js file. So I can’t take advantage of any of those methods. What I would like to do within the Post.js file is to be able to reference the other functions within the same class like so:
define([
'../environment',
'loglevel',
'../utility/Utility',
'jquery'
], function (EnvironmentModule, log, UtilityModule, $) {
'use strict';
var module = function () {
var environment,
utility,
api;
environment = new EnvironmentModule();
utility = new UtilityModule();
this.postToAPI = function (event, payload, callback) {
var apiEndpoint,
requestIdString,
eventAndAction;
apiEndpoint = environment.getEnvironmentPath().API;
requestIdString = utility.String.generateRandomString(32);
/*** THIS IS HOW I WANT TO CALL THE HELPER METHODS ***/
eventAndAction = this.Helper.matchAPIactionToEvent(event);
/*** THIS IS HOW I WANT TO CALL THE HELPER METHODS ***/
payload.event = eventAndAction.event;
payload.action = eventAndAction.action;
payload.requestID = requestIdString;
payload = $.param(payload);
$.post(apiEndpoint + '?' + payload, function (result) {
if (callback) {
callback(result);
}
});
return;
};
};
return module;
});
I figured out a working solution to the this, where I pass '../api/Helper' as one of the array values in the define statement of Post.js; but I don’t want to do that. What I want is to have Post.js be able to access any method from any other modules that are contained within the same directory. That way I don’t have to explicitly define them. It seems wrong to instantiate a new ApiModule() within Post.js. Here’s the directory structure:
Modules/api/Api.js
Modules/api/Post.js
Modules/api/Helper.js
...
I hope that makes sense. Is this possible?
Since you want any of the child modules to access any other child modules, what you could do is pass the parent module as an argument to the constructors of the child modules:
var module = function () {
this.Post = new PostModule(this);
this.Helper = new HelperModule(this);
};
Have the child modules store this information:
var module = function (parent) {
this.parent = parent;
Then use this this.parent to call the methods on other child modules:
eventAndAction = this.parent.Helper.matchAPIactionToEvent(event);
Note that if you would load '../api/Helper' with RequireJS in Post.js as you were thinking of doing, you would not be able to use the same object instance as the one defined on the parent module. You'd have to construct a new object for use in Post.js.

TypeScript - she's gotta have it? (where it == global scope)

I'm converting an Angular app to use TypeScript, but this is a general TypeScript question, not about Angular.
The angular js files are along the lines:
(function () {
var app = angular.module('myModule', []);
app.controller('myController', ['$scope',
function ($scope) {
$scope.myNewProperty = "Bob";
}
]);
})();
And I have converted that to lovely TypeScript class syntax:
class myController {
constructor($scope) {
$scope.myNewProperty = "Bob";
}
}
angular.module('myModule', []).controller("myController", myController);
All works fine, except the generated JS is not wrapped according to the JS module pattern (i.e. in an outer anonymous function):
var myController = (function () {
app.controller('myController', ['$scope',
function ($scope) {
$scope.myNewProperty = "Bob";
}
]);
})();
var app = angular.module('myModule', []);
So myController is now global. If I put the class in a TypeScript module, then the js generates with the module name as a global variable:
var TsModule;
(function (TsModule) {
var myController = (function () {
app.controller('myController', ['$scope',
function ($scope) {
$scope.myNewProperty = "Bob";
}
]);
})();
var app = angular.module('myModule', []);
})(TsModule || (TsModule = {}));
How do I stop TypeScript polluting the global scope in this way? I just want it all wrapped in a nice local scope.
I have seen it said elsewhere "just go back to the old JS syntax" (i.e. no TypeScript class).
How can I define an AngularJS service using a TypeScript class that doesn't pollute the global scope?
But the whole point of us using TypeScript /is/ the class syntax. Is TypeScript at odds with any (sensible) JS programmer who knows not to go global?
You can simply wrap your class inside a module
The module itself will be global, but if you don't export the class, there is little worry about polluting the global scope with a single module name.
So this TypeScript:
module MyModule {
class MyClass {
constructor(){}
}
}
Will produce the following JS:
var MyModule;
(function (MyModule) {
var MyClass = (function () {
function MyClass() {
}
return MyClass;
})();
})(MyModule || (MyModule = {}));
Quick Update
In the latest versions of TypeScript you can nest a class inside an IIFE:
(() => {
class Example {
}
})();
The resulting output is:
(function () {
var Example = (function () {
function Example() {
}
return Example;
}());
})();
Original Answer
You can avoid adding to the global scope using a module pattern such as AMD or CommonJS. When you use either of these, each TypeScript file is considered an external module and is kept out of global scope.
This example removes Angular for the purposes of the example, but while RequireJS adds the define method to global, none of your code is placed in this scope.
MyModule.ts
export class myController {
constructor($scope) {
$scope.myNewProperty = "Bob";
}
}
app.ts
import MyModule = require('MyModule');
var controller = new MyModule.myController('');
HTML
<script src="Scripts/require.js" data-main="app.js"></script>
What app.js looks like:
define(["require", "exports", 'MyModule'], function (require, exports, MyModule) {
var controller = new MyModule.myController('');
});
Alternatively... as you know... you can still implement it all using the JavaScript you are already using if you wish - you'll still get auto-completion and type checking, which are major benefits even if you aren't getting classes.
Josh is correct. Use module. As well, it is probably a good idea to use the grunt or gulp uglifyer that has an option to wrap your entire application in a closure at build time.
This is not an answer to your question, but more a suggestion.
On a side note, consider this syntax for controllers
module myModule {
// We export the class so that we can access its interface in tests.
// The build time gulp or grunt uglify will wrap our app in a closure
// so that none of these exports will be available outside the app.
export class MyController {
myNewProperty = "bob";
// Next we will use Angulars $inject annotation to inject a service
// into our controller. We will make this private so that it can be
// used internally.
static $inject = ['someService'];
constructor(private someService: ng.ISomeService) {}
// we can access our class members using 'this'
doSomething() {
this.someService.doSomething(this.myNewProperty);
}
}
angular.module('app').controller('MyController', MyController);
}
Along with this syntax you would use the controllerAs syntax.
Just as in JavaScript, you can fully avoid namespace pollution by wrapping your declarations inside an immediately invoked function expression.
So the original JavaScript:
(function () {
var app = angular.module('myModule', []);
app.controller('myController', ['$scope',
function ($scope) {
$scope.myNewProperty = "Bob";
}
]);
})();
becomes the following TypeScript:
(function () {
class MyController {
static $inject = ['$scope'];
contructor($scope: ng.IScope & { myNewProperty: string }) {
$scope.myNewProperty = 'Bob';
}
}
angular.module('myModule', [])
.controller('MyController', MyController);
})();
Note that this introduces no names into the surrounding scope. Actually, the original JavaScript is perfectly valid TypeScript, but it does not take advantage of TypeScript.
Also note that I edited slightly for style.
At any rate, if you are not using modules with a module loader such as RequireJS, SystemJS, or what have you, you can still avoid namespace pollution by following the tried and true IIFE pattern. This is my recommendation.

Is it possible to have variables visible "only to modules" and its extensions?

Say I have the following modules, split across multiple files both capable of extending skillet:
File1.js:
(function(){
var privateVar1 = 0;
var privateFunction1 = function() {
//function definiton
};
skillet.fry() = function() {
//fry it
//matchbox.light();
};
})(window.skillet = window.skillet || {});
File2.js:
(function(){
var privateVar2 = 0;
var privateFunction2 = function() {
//some private function
};
skillet.grillIt = function() {
//grill It
//matchbox.strike(); <-- Shared with File1.js
};
})(window.skillet = window.skillet || {});
Is it possible to have a shared variable/object like matchbox be sharable by the two modules without being bound to window.matchbox or window.skillet.matchbox? I.e. the visibility of matchbox should only be to File1.js and File2.js and must not be accessible elsewhere. I doubt if it's possible, but is there a way to achieve such a behavior in JavaScript? If not, what's the best practice to use in this regard?
(It's more like having a shared event-bus among a set of related modules without exposing that bus globally)
Nope.
"private" variables work in JS only because of the scope that the function was declared in. There is no way to share that scope with a function declared in an entirely different scope. Scope is an unchangeable property of functions, once they are created.
This is why this sort of thing is usually done with _foo style properties.
skillet._matchbox = { strike: function() { ... } };
The underscore prefix is convention for "internal" and serves as a hint not to mess with it.
You could also get creative with how you pass matchbox around though, though in all cases it will mean providing a way to get matchbox out it's original scope. Like perhaps, makes a skillet.extend method that passes the matchbox to it's argument?
(function() {
var matchbox = { strike: function() { ... } }
window.skillet = {
extend: function(fn) {
fn(matchbox);
}
};
})();
skillet.extend(function(matchbox) {
var privateVar2 = 0;
var privateFunction2 = function() {};
skillet.grillIt = function() {
//grill It
matchbox.strike();
};
}
Which allows you to use matchbox outside it's original scope in a controlled way. But it also allows anyone to get matchbox that maybe shouldn't.
var stolenMatchbox;
skillet.extend(function(matchbox) {
stolenMatchbox = matchbox;
});
while (stolenMatchbox.count > 0) { stolenMatchbox.strike(); }
alert("Now you are outta matches, sucker!");
Since you are already dividing your code into multiple files, you may look into using a module loader like require.js. You could define a third module for the matchbox and then pass it in as an argument to the two skillets in your example above. With this approach, you won't have to globally expose the matchbox via the window.
File1.js with require.js would look like this:
define(['matchbox'], function(matchbox){
(function(){
var privateVar1 = 0;
var privateFunction1 = function() {
//function definiton
};
skillet.fry() = function() {
//fry it
matchbox.light();
};
})(window.skillet = window.skillet || {});
});
matchbox.js would look something like this:
define([], function() {
function light() {
//light implementation
}
function strike() {
//strike implementation
}
return {
light: light,
strike: strike
}
}

Categories