Today I've adopting Browserify for my AngularJS project, but there is something that's very unclear to me. In all examples and blog posts I've seen stuff like this:
/app.js:
require('./messages');
angular.module('sling', ['sling.messages']);
/messages/index.js:
exports = angular.module('sling.messages', [])
.controller('MessagesListCtrl', require('./MessagesListCtrl'));
/messages/MessagesListCtrl.js:
module.exports = function() {
// ...
});
Sure, this works, but why do this? I've implemented it like this and that works absolutely fine too and feels more normal for an AngularJS project:
/app.js:
require('./messages');
angular.module('sling', ['sling.messages']);
/messages/index.js:
angular.module('sling.messages', []);
require('./MessagesListCtrl');
/messages/MessagesListCtrl.js:
angular.module('sling.messages').controller('MessagesListCtrl', function() {
// ...
});
In other words, I'm completely skipping the exports/module.exports, only using require to basically include the files with the controllers, services, filter, etc.
Am I doing this right? I mean it all works, but am I going to be in trouble later on?
The main (and honestly only) reason to use Browserify is if you want to have CommonJS modules (i.e. NodeJS modules) in the browser. A CommonJS module stays out of the global scope by having an implicit 'module' scope. You choose what to expose from the module scope (usually the entry point or primary function of your module) into by augmenting the 'exports' object that every module has.
So a 'real' CommonJS module looks like this.
File A:
// a.js
function doSomething() {
console.log("I am doing something");
}
module.exports = doSomething
File B:
// b.js
doSomething();
// Exception - attempt to call a non-existent function
File C:
// c.js
var doSomething = require('a');
doSomething();
// logs "I am doing something"
In the browser which has no module scope, a.js would be augmenting the global scope with the doSomething function, since it's declared as a global. Browserify works around this by wrapping each bundled module into a function wrapper, and supplying an 'exports' object to the included modules as an argument to this wrapper.
Enter AngularJS. You've got two approaches you can use here, of which I'm assuming from the fact that you're not using require('angular') is the first:
Include AngularJS as a hard-coded script available in your index.html before your Browserify-transpiled bundle script.
Shim AngularJS so that it is attached to an exports object rather than the window (using something like browserify-shim), then importing it using require like any other module.
I tend to prefer the second approach because it's kind of weird to use Browserify to give you module scope and then make the major dependency of your project a window global.
However, AngularJS already has its own dependency-injection driven module system. When you declare angularJS components, you're attaching them to module objects which themselves have been attached to the angular object. This means that the exports object for your angularJS module files is essentially redundant in the case of Angular, since as long as the file executes then the angular object will be augmented with your module and components.
You still need to 'require' the files since otherwise Browserify won't bundle them, they'll never execute, and they'll never augment the angular object with your modules. But you don't need to add anything to the exports for Angular, since the angular object is your exports.
So why did I spend all this time explaining how CommonJS modules and exports work? Because hopefully the other reason why you are using Browserify is to allow you to make use of modules hosted on NPM in your browser application. Most of those modules are non-angular commonJS modules, meaning their functionality is exposed via an exports. In this case it's important to capture their exports in a variable when you require them, such as I'm doing in c.js above. Similarly, if you write some modules and release them to NPM, your users are going to expect you'll be adding the entry point to your module to the exports object of the file declared as main in your package.json.
Related
After reading hundreds of lines about browserify vs webpack and several how to of both I decided to go for webpack. The main reason it's because I liked the idea of bundling everything into a js file.
I have an angular project already working and I want to refactor it for webpack. The problem? My project is using angular 1.4.7, ng-animate and plain javascript (ES5) and all the tutorials and manuals are for ES6. I don't want to refactor my project so much. What's the way to go? I would like an example of each angular module : factory, directive, controller and so on. Many thanks
I typically have a feature.module.js file which has my module definition and requires all of the directives / services contained within the module. Also has the external dependancies.
/* module.js */
angular.module('my.module',['dependancy1', 'dependancy2']);
//External libraries
require('./dependancy1.module.js');
require('./dependancy2.module.js');
//Internal components
require('./thing.directive');
require('./thing.service';
/* service.js */
angular.module('my.module')
.directive('yourDir', function myDir(){...});
I'm dealing with the same problem now. And I found something that works (work in progress, but at least I can see progress). My steps:
Install yeoman
Run this angular-webpack generator. Select 'ES5' when asked (the other option is 'ES2015', which I guess is the same that 'ES6')
Start modifying the automatically generated boilerplate with your Angular code
Yes, you still need to learn about gulp and sass, but at least you can run a simple AngularJS app using the old ES5 syntax, and start modifying it.
I'm probably blogging about this. So, I'll update this answer then.
I tend to do this:
app.js:
require('/any/angular/deps');
var subModule = require('/some/sub/module');
var app = angular.module('myApp', []);
// pass the app module in sub modules to allow them to define their own config
subModule.configure(app);
/subModule/module.js:
var someSubDirective = require('./subDir/directive');
export function configure(app) {
someSubDirective.configure(app);
}
/subModule/subDir/directive.js:
export function configure(app) {
app.directive('myDir', myDir);
}
function myDir() {
}
My idea is to let all sub modules handle their own configuration, so declaring config or constant, factories or providers. Letting this then bubble up to the app.js. This means its really easy to delete a folder from your structure, because it is one line removal from it's parent module.
This also makes relevant file paths a lot shorter and easier to handle.
In a very basic Angular app we have
<head>...
<script src="app.js"></script>
<script src="maincontroller.js"></script>
App defines the app module, and controller then hangs off app as app.controller("MainController..") If the order of the two scripts is reversed, the app will throw an error "app is not defined".
Is there a way around this load order dependency? My fear is that as it becomes more complex, I will get script order dependencies. And perhaps I might want to load my scripts async as well.
Thank you.
See, for example, this plunker: http://plnkr.co/edit/kqVqTHxl4tc6mIV5bDbQ
I've defined SomeService in an own file, svc.js. I've defined the module and the main controller in app.js. And even though the MainController depends on that service that "loads later", the dependency is figured out, and injected. All you should worry about is: put the module definition first.
Also: Don't store the application inside a global variable called app, instead, use angular.module with the name of the module to retrieve a reference to it:
angular.module('SomeModuleName').controller(...)
Any kind of global variables are generally not a good practice.
Look at using Angular and Browserify or Angular and RequireJS. Browserify and RequireJS are module loaders that let you keep 1 script reference in your index.html.
Browserify is based on a build step that will bundle all your JS into 1 file.
RequireJS is an Asynchronous Module Definition (AMD) loader, that can load your files asynchronously.
I didn't understand how work modular depending.
I have 3 modules, they are dependent on each other, as shown in the picture.
"App" module includes "module1" and "module2".
"module2" includes "core" module. There are source on plunker.
angular.module("core", []).factory("HelloWorld", function() {
return function () {
alert('Hello World!')
}
});
angular.module("module1", []).controller("main", function(HelloWorld){
HelloWorld();
});
angular.module("module2", ["core"]);
angular.module("app", ["module1", "module2"]);
If I inject service from module core to module "module1" it is work fine. But "core" module not depend in module "module1". Why it happening?
Since your App module depends on Core module (indirectly through Module 2), the services in Core module are available anywhere inside your App module (including Module 1).
This is because Angular will first load all modules and then start instantiating their components and resolving injected dependencies.
Yet, if you indeed need Core services in Module 1, you should make it dependent on the Core module as well. That way your application won't break if Module 2 is modified at a later time (or removed altogether) and your Module 1 will be more self-contained and reusable (e.g. you could use it with a different application that does not depend on the Core module).
In general, you should not rely on "indirect" dependencies. Each module should explicitly declare its dependencies.
Angular is smart enough to only load a module if it is not already loaded, so there is no overhead.
Quoting from the Developer Guide's section on modules:
Modules can list other modules as their dependencies. Depending on a module implies that required module needs to be loaded before the requiring module is loaded. In other words the configuration blocks of the required modules execute before the configuration blocks of the requiring module. The same is true for the run blocks. Each module can only be loaded once, even if multiple other modules require it.
(emphasis mine)
When setting up a unit test suite for an angular application using Karma/Jasmine, is it recommended to include the js with the app module's config function in the test's files?
I've read that it is suggested to exclude this from testing, however that seems awkward because there's often critical setup that happens in the config function that would prevent the application from working.
What's the best practice around this? Create a mock config function that does the same thing in a 'mocked' manner?
I'm running across this issue myself but want to understand the broader strategy:
How do unit test with angular-translate
In my application, I ended up using the following solution:
Define an "appBase" module with all the config and run functions that I want to run when unit-testing and create another "app" module which declares "appBase" module as a dependency and includes all the config and run functions that I don't what to run when unit-testing. Then all my unit tests use the "appBase" module, while the final application uses the "app" module. In code:
angular.module('appBase', ['dependencies'])
.config(function() {
// This one will run when unit-testing. Can also set-up mock data
// that will later be overridden by the "app" module
});
angular.module('app', ['appBase'])
.config(function() {
// This function will only run in real app, not in unit-tests.
});
I'm using grunt-browserify and running into two issues in particular. The task is up and running successfully with the following config options. The variable jsFilesToConcat represents all of the javascript files for a Backbone.js + Marionette.js application, the main application defintion, the front-end utility assets (e.g. Bootstrap plugins), and all JS associated with the project. Is this the wrong approach? The thought was to load the entire 250k JS application (and all it's dependencies) at one time.
I want to offer the disclaimer that this is new territory for me, so I think my intended use case is available with the options already available with the plugin, but I'm confused by two errors:
1) Backbone not defined - which means that the script is in fact loading, however, when I inspect the call stack in Chrome Dev Tools it shows only the anonymous self-invoking function. So I'm not clear on how to pass the Backbone object to Marionette in order for it to be extended at load time.
2) require is not defined - error on the line where I'm declaring var SampleApp = require('SampleApp'). Do I need to do something special within my grunt config, or node.js server.js config to expose the require function?
3) Is the javascript executing asynchronously within itself, is this part of the browserify intended behavior that I'm not properly handling? I think since I'm wrapping alot of JS utilities in a global wrapper to protect namespacing, that's the reason some functions are not available, but I'm not clear on why that would affect require.
// uses grunt-browserify task
browserify: {
developmentJs: {
options: {
debug: true,
alias: ["./js/app.dev.js:SampleApp"],
},
src: [
'<%= pkg.jsFilesToConcat %>'
],
dest: 'public-dev/js/app.dev.js'
}
}
and then in the index.html of my single-page Marionette app, I have.
(function ($) {
$(document).ready( function() {
var sampleApp = require('SampleApp');
console.log( SampleApp );
});
})(jQuery);
Well for starters, the src attribute in your grunt file doesn't need to reference all of the files in your application. It only needs an entry point. So normally I have something similar to your anonymous self executing function in an index.js file, and set my src configuration option to ["./index.js"]. When browserify looks at that file, it will check for calls to require and grab all of the required dependencies.
That said, browserify will generate a file with an internal definition of require. The require function is not globally available on the page, nor are the dependencies that you include with require. You can use them in your application, but that doesn't make them available in the page. So if you are getting a Backbone is not defined error, the first thing I would check is that you have installed backbone via npm (npm install backbone --save).
Once everything is set up you should just have to include your compiled script on the page, and let your anonymous self executing function (which should now be in a file that grunt-browserify is processing) do the work to kick off your application.