Premise
Let's say I have two different AMD-based AngularJS apps, each of them with their own sets of controllers, directives, services, etc. Each of them are bundled in their own dist/{app-name}.min.js and loaded in <script> tags in the same HTML page (this is all in the context of a CMS that then contains these apps among other things)
Now the apps end up sharing some of the services, directives, and vendor libraries like angular itself, moment, jQuery, etc, so I've made another folder for all of these resources, which results in a bundle that will be added to the page before the js bundles of the apps:
<script src="/some-path/dist/shared-resources.min.js"></script>
<script src="/some-path/dist/first-app.min.js"></script>
<script src="/some-path/dist/second-app.min.js"></script>
This is the resulting folder structure:
.
├── shared-resources/
│ ├── dist/
│ ├── src/
│ │ └── common/
│ │ ├── directives/
│ │ ├── modules/
│ │ ├── services/
│ │ └── vendor/
│ └── build.js
│
├── first-app
│ ├── dist/
│ ├── src/
│ │ ├── first-app/
│ │ │ ├── controllers/
│ │ │ ├── modules/
│ │ │ ├── services/
│ │ │ ├── directives/
│ │ │ └── app.js
│ │ └── first-app.js
│ └── build.js
│
└── second-app
├── dist/
├── src/
│ ├── second-app/
│ │ ├── controllers/
│ │ ├── modules/
│ │ ├── services/
│ │ ├── vendor/
│ │ └── app.js
│ └── second-app.js
└── build.js
This is an example of what the build.js file for the common modules looks like
({
baseUrl: 'src',
removeCombined: true,
out: 'dist/shared-resources.min.js',
paths: { // forcing a `common/{modulename}` convention
'common/jquery': 'common/vendor/jquery.min',
'common/moment': 'common/vendor/moment.min',
'common/angular': 'common/vendor/angular/angular.min',
},
shim: {
'common/angular': {
exports: 'angular',
}
},
include: [
'common/modules/vendors', // Just a bundle of every vendor modules
'common/directives/common-directive',
'common/services/common-service'
],
})
Now my intention was to have all the shared modules being namespaced with common/, so each of the apps could require common/angular, common/directives/common-directive, and so on, and then exclude the common path when creating their bundle (since all the common modules are already present in the shared-resources.js bundle), for example:
// first-app/src/first-app/controllers/app-controller.js
define([
'first-app/modules/controllers',
'first-app/services/app-service',
'common/services/common-service'
], function (controllers) {
'use strict';
controllers.controller('AppController', ['CommonService', 'AppService', function (CommonService, AppService) {
CommonService.call();
AppService.call();
}]);
});
// first-app/build.js
({
baseUrl: 'src',
out: 'dist/first-app.min.js',
paths: {
'common': 'empty:'
},
name: 'first-app',
deps: ['first-app/app']
})
Problem
The problem is how these two apps, which again are both loaded on the page (this can't be avoided), are supposed to correctly look up these common modules.
Given that each of the apps have obviously a different baseUrl, they are put in different RequireJS contexts, otherwise the baseUrl of the second app would override the baseUrl of the first one, causing the incorrect loading of its modules:
// first-app/src/first-app.js
require.config({
context: 'first-app',
baseUrl: 'first-app/src',
})(['fist-app/app']);
// first-app/src/second-app.js
require.config({
context: 'second-app',
baseUrl: 'second-app/src',
})(['second-app/app']);
But putting them in context then causes the look up for the common modules to fail, as the modules are looked in the baseUrl of the context. Actually this happens only for the second app (second in order of loading), while the first app to be included in the page can load the common modules fine
Question
So how should I make the apps to correctly share the common modules? Am I approaching this wrong? Should I use something else than RequireJS?
The context feature of RequireJS is really meant to be used to handle a case where you have to load two conflicting applications on the same page and the conflict cannot be resolved otherwise. The way you've written your code so far may have led you to want to have two baseUrl values, but there is nothing in your question that indicates that you must have two baseUrl values. There are ways to avoid it:
Modules that are part of a logical set of modules should load each other with relative paths. For instance, the module you give as example could be:
// first-app/src/first-app/controllers/app-controller.js
define([
'../modules/controllers',
'../services/app-service',
'common/services/common-service'
], function (controllers) {
paths can be set to make it look like a module is directly under baseUrl even if it is not. You could have:
paths: {
'first-app': 'first-app/src'
'second-app': 'second-app/src'
}
and yes, loading first-app/app will work. RequireJS will transform the path to first-app/src/app.js.
Related
I'm having a hard time getting the require.js build just right. I have a main module and then the other pages/modules are lazy loaded. When it's done compiling, I have to fix the compiled dist/main.js or the app will load the compiled main module from the dist folder, but other modules are still loaded from the app folder. I have to change the require.config baseurl from /app to /dist. What do I need to reconfigure to get it to build correctly?
Directory Structure
├── app
│ ├── modules
│ │ ├── example_module
│ │ ╰── another_module
│ │ ├── AnotherController.js
│ │ ╰── AnotherView.stache
│ ├── main.js
│ ╰── build.js
├── dist
│ ├── modules
│ │ ├── example_module
│ │ ╰── another_module
│ │ ╰── AnotherController.js
│ ╰── main.js
├── content
│ ├── css
│ │ ╰── main.css
│ ├── sass
│ │ ├── table.scss
│ │ ├── type.scss
│ │ ├── form.scss
│ │ ╰── main.scss
│ ╰── img
├── lib
│ ├── bootstrap
│ ╰── canjs
├── bower.json
├── gulpfile.js
├── package.json
├── README.md
╰── index.html
app/main.js
require.config({
baseUrl: '/app', // must change this after compilation!
paths: {
'jquery': '../lib/jquery/dist/jquery.min',
'jquery-easing': '../lib/jquery-easing-original/jquery.easing.1.3.min',
'jquery-throttle': '../lib/jquery-throttle-debounce/jquery.ba-throttle-debounce.min',
'jquery-inputmask': '../lib/jquery.inputmask/dist/jquery.inputmask.bundle.min',
'can': '../lib/canjs/amd/can',
'bootstrap': '../lib/bootstrap-sass-official/assets/javascripts/bootstrap',
...
},
shim: {
'jquery-easing': ['jquery'],
'jquery-throttle': ['jquery'],
'bootstrap': ['jquery']
...
}
});
require([...], function (...) {
// Init App
});
app/build.js
({
appDir: '.',
baseUrl: '.',
dir: '../dist',
mainConfigFile: 'main.js',
preserveLicenseComments: false,
modules: [
{
name: 'main',
include: [
'modules/dashboard/DashboardController',
...
]
},{
name: 'modules/example_module/ExampleController',
exclude: ['main']
},{
name: 'modules/another_module/AnotherController',
exclude: ['main']
},{
...
}
]
})
Interesting, I've actually not used this scenario with RequireJS, however this structure would make sense for bundles/progressively loading files.
What I've done in the past is one of two things:
1) Use the existing /app directory for progressively loaded modules. /dist would only contain main.js/css or output the minified files to the root(if it's only 1-2 files)
2) Re-create the entire structure with only necessary files inside /dist. For example: /dist/index.html, /dist/app/modules/*, /dist/main.js would all exist. This way you can copy the entire /dist contents to any deployment package you use, vs cherry-picking which files you'll need on a production server.
Typically, I've found #2 is more common in my experience.
I'm trying to setup a glob array for my javascript concat build task in gulp. The directory structure looks as follows:
├── about
│ └── about.js
├── assets
├── contact
├── core
│ ├── navbar
│ │ ├── navbar.js
│ │ └── navbar.test.js
│ ├── routing.js
│ ├── routing.test.js
│ ├── utils.js
│ └── utils.test.js
├── generated
│ ├── footer.js
│ ├── header.js
│ └── templates.js
├── home
├── app.js
└── config.js
The order of the files is important:
generated/header.js
app.js
any of the *.js files, except those here below
generated/templates.js
generated/footer.js
I've wildly tried all kinds of wildcards combination, but the globbing isn't strong with me.
var inputFiles = [
'generated/header.js',
'app.js',
'!(generated)**/*.js', // <=---- ???
'generated/templates.js',
'generated/footer.js',
'!**/*.test.js'
];
So how do I include all *.js files except those from a subdirectory?
Thanks.
The best I came up with:
var gulp = require('gulp');
var tap = require('gulp-tap');
gulp.task('default', function() {
return gulp.src([
'generated/header.js',
'app.js',
'*.js',
'./!(generated)/**/*.js', // <- All subdirs except 'generated'
'generated/{templates,footer}.js',
'!**/*.test.js',
'!node_modules/**'
]).pipe(tap(function(file) {
console.log(file.path);
}));
});
Running it:
∴ glob-test gulp
[20:07:51] Using gulpfile ~/Desktop/glob-test/gulpfile.js
[20:07:51] Starting 'default'...
/Users/heikki/Desktop/glob-test/generated/header.js
/Users/heikki/Desktop/glob-test/app.js
/Users/heikki/Desktop/glob-test/config.js
/Users/heikki/Desktop/glob-test/gulpfile.js
/Users/heikki/Desktop/glob-test/about/about.js
/Users/heikki/Desktop/glob-test/core/routing.js
/Users/heikki/Desktop/glob-test/core/utils.js
/Users/heikki/Desktop/glob-test/core/navbar/navbar.js
/Users/heikki/Desktop/glob-test/generated/templates.js
/Users/heikki/Desktop/glob-test/generated/footer.js
[20:07:51] Finished 'default' after 326 ms
The main trick is avoiding the "!" character at the beginning of glob when including files.
https://github.com/isaacs/minimatch#comparisons-to-other-fnmatchglob-implementations
"If the pattern starts with a ! character, then it is negated."
ps. Placement of the negated globs doesn't matter. They are always moved to the end behind the scenes.
I am using Jquery UI, Bower, and RequireJS in a project and am having difficulty configuring Jquery UI with Require. Basically Jquery UI is trying to find its dependencies in the root directory, and not where they are located, giving me an error.
I understand as of 1.11 Jquery UI supports AMD and the documentation notes that it works with a directory like so:
├── index.html
├── js
│ ├── app.js
│ ├── jquery-ui
│ │ ├── accordion.js
│ │ ├── autocomplete.js
│ │ ├── button.js
│ │ ├── core.js
│ │ ├── datepicker.js
│ │ ├── dialog.js
│ │ └── ...
│ ├── jquery.js
│ └── require.js
However, using Bower, the directory is more like
├── index.html
├── js
│ ├── app.js
|── bower_componenents
│ ├── jquery-ui
│ │ ├── accordion.js
│ │ ├── autocomplete.js
│ │ ├── button.js
│ │ ├── core.js
│ │ ├── datepicker.js
│ │ ├── dialog.js
│ ├── jquery
│ │ ├── jquery.js
│ └── require
│ │ ├── require.js
How does one configure Require so that Jquery UI can find its dependencies?
I was able to get it partially working by aliasing bower_components with the path attribute in the requirejs config. But the internal jquery libs are assuming that all of the javascript files are lumped together in the same directory structure (relative references) so it still doesn't work.
I'm considering using Grunt to copy my dependencies under my js directory so that everything is on the same baseUrl. Not exactly ideal, but it would probably make it easier to use r.js or gulp later on.
I was just setting it up wrong. I got it working after changing my config file to
require.config({
shim: {
underscore: {
exports: '_'
},
backbone: {
deps: ['underscore', 'jquery'],
exports: 'Backbone'
},
backboneLocalstorage: {
deps: ['backbone'],
exports: 'Store'
},
paths: {
jquery: '../bower_components/jquery/dist/jquery',
'jquery-ui': '../bower_components/jquery-ui/jquery-ui',
underscore: '../bower_components/underscore/underscore',
backbone: '../bower_components/backbone/backbone',
backboneLocalstorage: '../bower_components/backbone.localStorage/backbone.localStorage',
moment: '../bower_components/moment/moment',
text: '../bower_components/requirejs-text/text'
}
});
require(['backbone', 'views/app-view', 'routers/router'],
function (Backbone, AppView, Workspace ) {
new Workspace();
Backbone.history.start();
new AppView();
});
and then defining in my backbone view like so
define([
'collections/todos-collection',
'views/todo-view',
'common',
'backbone',
'jquery',
'jquery-ui'
], function(Todos, TodoView, Common, Backbone, $){
As you can see, jquery-ui will attach itself to the $ symbol.
Hope this helps.
I'm working to cleanly structure my AngularJS app according to best practices, which includes separating the controllers and app into different script files.
Quick question: where should I put my factories and services? I am asking in the context of having factories & services that will be accessed outside of the scope of a single controller as well as having some that are within the scope of a single controller.
Update: the immediate answer below is no longer correct. Please see the latest addendum (written March 1, 2015) to this answer.
Got it! According to Brian Ford's article on Building Huuuuuuuge Angular Apps, the best practice appears to be to connect services and factories to the app in a separate file, like so:
root-app-folder
├── index.html
├── scripts
│ ├── controllers
│ │ └── main.js
│ │ └── ...
│ ├── directives
│ │ └── myDirective.js
│ │ └── ...
│ ├── filters
│ │ └── myFilter.js
│ │ └── ...
│ ├── services
│ │ └── myService.js
│ │ └── ...
│ ├── vendor
│ │ ├── angular.js
│ │ ├── angular.min.js
│ │ ├── es5-shim.min.js
│ │ └── json3.min.js
│ └── app.js
├── styles
│ └── ...
└── views
├── main.html
└── ...
(PSST! In case you're wondering, Brian Ford is part of the AngularJS team, so his answer seems pretty legit.)
Addition (April 24, 2013)
This just in: Yeoman is a fantastic tool for generating apps with the proper directory structure for big, functional Angular apps. It even has Grunt & Bower packed in!
Addendum (March 1, 2015)
According to a comment via PaoloCargnin, Google actually recommends a different structure, as detailed by this document. The structure should look like this:
sampleapp/
app.css
app.js //top-level configuration, route def’ns for the app
app-controller.js
app-controller_test.js
components/
adminlogin/
adminlogin.css //styles only used by this component
adminlogin.js //optional file for module definition
adminlogin-directive.js
adminlogin-directive_test.js
private-export-filter/
private-export-filter.js
private-export-filter_test.js
userlogin/
somefilter.js
somefilter_test.js
userlogin.js
userlogin.css
userlogin.html
userlogin-directive.js
userlogin-directive_test.js
userlogin-service.js
userlogin-service_test.js
index.html
subsection1/
subsection1.js
subsection1-controller.js
subsection1-controller_test.js
subsection1_test.js
subsection1-1/
subsection1-1.css
subsection1-1.html
subsection1-1.js
subsection1-1-controller.js
subsection1-1-controller_test.js
subsection1-2/
subsection2/
subsection2.css
subsection2.html
subsection2.js
subsection2-controller.js
subsection2-controller_test.js
subsection3/
subsection3-1/
etc...
I'm building an in-browser MVC application which will eventually run on a mobile device via PhoneGap. The app will communicate with the API server but will otherwise be completely independent. When I develop standard server-side MVC applications in Rails, the models, views, and controllers are separated into distinct files and directories. What's the standard practice with in-browser MVC apps? Are the MVC components usually defined within a single JS file, or are they usually separated out?
During the development phase, yes javascript files should be separated and well documented..
You can use requirejs to load your modules/views/collections separately.
Here is a great tutorial about Asynchronous Module Definitions (AMD). It's mainly about how you would organize your application using modules. I suggest you read it.
Below is the sample project structure the tutorial's author uses:
├── js
│ ├── libs
│ │ ├── jquery
│ │ │ ├── jquery.min.js
│ │ ├── backbone
│ │ │ ├── backbone.min.js
│ │ └── underscore
│ │ │ ├── underscore.min.js
│ ├── models
│ │ ├── users.js
│ │ └── projects.js
│ ├── collections
│ │ ├── users.js
│ │ └── projects.js
│ ├── views
│ │ ├── projects
│ │ │ ├── list.js
│ │ │ └── edit.js
│ │ └── users
│ │ ├── list.js
│ │ └── edit.js
│ ├── router.js
│ ├── app.js
│ ├── main.js
│ ├── order.js
│ └── text.js
└── index.html
For the validation/deployment phase, use a grunt-like tool to launch automated tasks. Such as concatenating and minifying javascript files into a single one. (It takes around 30 seconds depending on how you've configured it)
Here is an example of a grunt file.