I have a large AngularJS/Express app where I'd like to begin sharing code between client and server, mainly small utility libraries, e.g:
// Name: utilities.js
module.exports.testUtilities = function () {
console.log('testUtilities: Hello world!');
};
I'm now trying to set up Browserify using grunt-browserify:
// Name: Gruntfile.js
browserify: {
client: {
src: ['crossplatform/**/*.js'],
dest: 'app/scripts/crossplatformBrowserify.js'
}
},
I successfully build a crossplatformBundle.js which I include in my index.html.
But I fail at accessing my Browserified code from the AngularJS client:
// Name: MyAngularController.js
var utilities = require('./utilities');
utilities.testUtilities();
Error message: require is undefined.
I see many Browserify questions where the answer is to bundle up ALL the client-side scripts, but I'd rather avoid that if I can, since I want separate JS files in development mode.
Thankful for any tips I can get!
The solution was to define a “standalone” bundle for Browserify:
browserify: {
client: {
src: ['crossplatform/**/*.js'],
dest: 'app/scripts/crossplatform.js',
options: {
browserifyOptions: {
standalone: 'crossplatform'
},
}
}
},
...which allows me to call the method with window.crossplatform.testUtilities() in the browser.
Update: see this thread for more examples of how to bundle multiple libraries/modules.
Related
I've recently joined a project which is built using Backbonejs, (uses Marionette for view rendering) + nodejs. They also use requirejs to load the backbonejs files. Would like to add at this stage, that I've never worked with backbonejs or requirejs before and hence I'm struggling with the issue I describe later.
Some code that will help explain the issue that I run into (All this code was already written by previous dev's)
Folder Structure:
/public
/js
/collection (consists all Backbone.js collections files)
/lib
/bower_components
/backone
/marionette
/etc
/models (consists all Backbone.js models files)
/views (consists all Backbone.js view files)
/main.js
/main.build.js
/app.js
/controller.js
/router.js
Code from files that I think relate to issue:
main.js
requirejs.config({
paths: {
'async': 'lib/bower_components/requirejs-plugins/src/async',
'jquery': 'lib/bower_components/jquery/dist/jquery.min',
'underscore': 'lib/bower_components/underscore/underscore-min',
'lodash': 'lib/bower_components/lodash/dist/lodash.min',
'backbone': 'lib/bower_components/backbone/backbone',
'marionette': 'lib/bower_components/marionette/lib/backbone.marionette.min',
'markercluster':'lib/markercluster',
'jquerymobile': 'lib/jquery.mobile-1.4.0.min',
'hogan': 'lib/template-2.0.0.min',
'templates': '/templates',
'real': 'lib/mainjs',
'touch': 'lib/jquery.touchSwipe.min',
'mouse': 'lib/jquery.mousewheel',
'moment': 'lib/moment-2.5.1.min',
'humanize': 'lib/bower_components/humanize-plus/public/dist/humanize.min',
'validator': 'lib/bower_components/validator-js/validator.min',
'real': 'lib/mainfile'
},
shim: {
backbone: {
deps: ["underscore"]
},
marionette: {
deps: ["backbone"]
},
templates: {
deps: ["hogan", "jquery"]
},
real: {
deps: ["jquery", "jquerymobile", "touch", "mouse"]
},
markercluster: {
exports: "MarkerClusterer"
},
humanize: {
exports: "humanize"
}
},
waitSeconds: 0
});
define('gmaps', ['async!http://maps.google.com/maps/api/js?v=3&key=AIzaSyBiV8f88yLWJ_IMSdP1fVNO1-gt3eLVSgg&sensor=true&callback=gMapsCallback'], function(){
// define('gmaps', ['http://maps.google.com/maps/api/js?v=3&sensor=false'], function(){
return window.google.maps;
});
require(['app', 'templates', 'real'], function(app) {
app.start({
version: "0.9.9"
});
});
main.build.js
({
baseUrl: ".",
name: "main",
wrapShim: true,
out: "main-built.js"
})
app.js
define(['underscore', 'controller', 'router', 'models/Cache', 'views/RootView'], function(_, Controller, Router, Cache, RootView) {
var Application = Marionette.Application.extend({
propertyListPageSize: 3,
initialize: function() {
_.templateSettings = { interpolate : /\{\{(.+?)\}\}/g };
},
onStart: function(options){
new RootView();
this.controller = new Controller();
this.router = new Router({controller: this.controller});
this.cache = new Cache();
this.context = {};
//this.evHistory = [];//#todo remove once BB/marionette navigation is in place
if(Backbone.history) Backbone.history.start({ pushState: false });
if(Backbone.history.fragment === "") this.navigate('home');
},
navigate: function(fragment, trigger, replace){
this.router.navigate(fragment, {trigger:trigger, replace:replace});
},
back: function() {
window.history.back();
}
});
app = new Application();
return app;
});
rootView.js
define(['marionette', 'views/HomeView', 'views/HeaderView', 'views/FooterView', 'views/MenuView', 'views/VideoView', 'views/LocationSearchView', 'views/LoginView', 'views/FindView', 'views/ServicesView', 'views/ValueView', 'views/PropertyListView', 'views/SideBySideView', 'views/ConfirmRegistrationView', 'views/ForgotPasswordView', 'views/CreateAccountView', 'views/UserHomeView', 'views/MyBrokerView', 'views/GiveFeedbackView', 'views/SeeFeedbackView', 'views/ViewingScheduleView', 'views/MyViewingsSummaryView', 'views/MyAccountView', 'views/ViewingConfirmView', 'views/ValueAddressPropertyListView'],
function(Marionette, HomeView, HeaderView, FooterView, MenuView, VideoView, LocationView, LoginView, FindView, ServicesView, ValueView, PropertyListView, SideBySideView, ConfirmRegistrationView, ForgotPasswordView, CreateAccountView, UserHomeView, MyBrokerView, GiveFeedbackView, SeeFeedbackView, ViewingScheduleView, MyViewingsSummaryView, MyAccountView, ViewingConfirmView, ValueAddressPropertyListView) {
var RootView = Marionette.LayoutView.extend({
...some view code
});
Use case I'm trying to solve:
So when I access the site in the browser, I notice in the debugger that it loads all the js files right at the beginning. During the load process my site is blank and user has to wait a while before he can use the site.
So what I've been able to understand is that when app is 'started' in main.js, app.js creates an instance of rootView.js , which in turn has all the views listed as dependencies. This triggers a download request for all the other views which in turn would solve their own dependencies and download all the relevant models and collections. Hence all files being downloaded when the user accessed the site.
Solution I've been trying:
Since requirejs is being used, I'm trying to use r.js to optimize and combine all the js files to reduce the number of downloads.
Issue I'm running into:
When i run r.js. i get the following error
Tracing dependencies for: main
Error: ENOENT: no such file or directory, open '/var/node_projects/rm/rm.src.server/src/public/js/underscore.js'
In module tree:
main
app
Error: Error: ENOENT: no such file or directory, open '/var/node_projects/rm/rm.src.server/src/public/js/underscore.js'
In module tree:
main
app
at Error (native)
If I add the underscore.js files directly to the specified path in the error, then I get the same error for marionette.js. What I think is happening is that app.js is not recognizing the shim'ed dependencies and hence its trying to find the files directly at specified path in the error.
Things I've tried:
- I've added wrapShim: true in the main.build.js file but that did not help
Honestly, I've been sitting on this for a couple of days and I'm not sure what I can do next and hence this post.
Any help/direction would be appreciated.
You need to include the same shim configuration in your build file, as wrapShim is not sufficient.
If shim config is used in the app during runtime, duplicate the config here. Necessary if shim config is used, so that the shim's dependencies are included in the build. Using "mainConfigFile" is a better way to pass this information though, so that it is only listed in one place. However, if mainConfigFile is not an option, the shim config can be inlined in the build config.
https://github.com/jrburke/r.js/blob/master/build/example.build.js
I am trying to compile all angulara templates into a single js file.
Something like what ember does with ember-cli.
So I successfully managed to minify and concat all the javascript files.
I have just 2 files now vendor.js and application.js and whole lot of template files which I want to cram into templates.js.
How do I go about it? If some one could give step by step explanation, please. Any links would be appreciated too.
Surprisingly there is no information about this anywhere.
I am using mimosa as build tool, it seemed to me the easiest.
Here is my mimosa config:
exports.config = {
modules: [
"copy",
"stylus",
"minify-css",
"minify-js",
"combine",
"htmlclean",
"html-templates"
],
watch: {
sourceDir: "app",
compiledDir: "public",
javascriptDir: "js",
exclude: [/[/\\](\.|~)[^/\\]+$/]
},
vendor: {
javascripts: "vendor/js"
},
stylus: {
sourceMap: false
},
combine: {
folders: [
{
folder:"vendor/js",
output:"vendor.js",
order: [
"angular.js"
]
},
{
folder:"js",
output:"main.js",
order: [
"application/main.js"
]
}
]
},
htmlclean: {
extensions:["html"]
},
htmlTemplates: {
extensions: ["tpl"]
},
template: {
outputFileName: "templates"
}
}
It does generate templates.js file without any errors. But when I link it, angular spits a bunch of errors.
Once compiled, how do I actually call those templates from ng-include and from the route provider?
I assume that it is the same as I would call a script template using the id which in my case is derived from template original file name, right?
Maybe I am missing some important steps.
The build tool is not important here although desirable. If some one could show how to do it manually without a build tool I would figure out the rest.
Thanks.
I'm using Gulp as my build tool, and in that, there's a plugin gulp-angular-templatecache which pre-compiles and registers all templates for your module in the angular $templateCache - no changes are required to any of the calling code to use these. EDIT: The Angular documentation for $templateCache explains how the templateCache works.
It might be worth reading through the documentation for gulp-angular-templatecache to see how that pre-populates the $templateCache to see if you can crib something that would work with your build process.
Here's my gulp task that does the job:
var templateCache = require('gulp-angular-templatecache');
gulp.task('buildjstemplates', function () {
return gulp.src(['public/javascripts/app/**/*.html'])
.pipe(templateCache({module: 'app'}))
.pipe(gulp.dest('public/javascripts/app/'));
});
Can someone please suggest how I can set this up. Here is the scenerio.
I am developing an angular app with an Azure Mobile Services back end.
In angular I have resources that point to the Mobile Service API path. I have a config factory that returns the base service path. I want the dev/staging slot to point to the -dev api and the production slot to point to the production API path.
I can create two config factories, but I'm not sure how to tell it to use the dev one in the staging slot and the production one in the production slot. For a .net app with server code I would use the config stuff, or maybe environmental variables, but this is all client side code.
I thought to just have two web sites, once deployed from a dev branch and one deployed from the master and have a different config... but once I merge dev to master then the config change merges too.
I'm not sure if it matters, but I am deploying from Visual Studio Online as part of the build.
How can I accomplish this?
I was able to solve this very same problem. I have development, testing and production environments and they each need to connect to different API end points. I was able to accomplish this using a grunt plugin called grunt-ng-constant
Basically once you install the plugin, modify your Gruntfile and inside the grunt.initConfig add something like this:
ngconstant: {
// Options for all targets
options: {
space: ' ',
wrap: '"use strict";\n\n {%= __ngModule %}',
name: 'config',
},
// Environment targets
development: {
options: {
dest: '<%= yeoman.app %>/scripts/config.js'
},
constants: {
ENV: {
name: 'development',
apiEndpoint: 'http://your-development.api.endpoint:3000'
}
}
},
production: {
options: {
dest: '<%= yeoman.dist %>/scripts/config.js'
},
constants: {
ENV: {
name: 'production',
apiEndpoint: 'http://api.livesite.com'
}
}
}
},
Register the Grunt task like so:
grunt.registerTask('serve', function (target) {
if (target === 'dist') {
return grunt.task.run(['build', 'connect:dist:keepalive']);
}
grunt.task.run([
'clean:server',
'ngconstant:development', // ADD THIS
'bower-install',
'concurrent:server',
'autoprefixer',
'connect:livereload',
'watch'
]);
});
Now every time you run grunt serve it will generate a config.js file that holds your development constants. You can configure different tasks such as grunt testing or grunt production to generate the testing or production constants instead.
Finally you add the config.js to your index.html like so:
<script src="/scripts/config.js" />
And register the config module in your app:
var app = angular.module('myApp', [ 'config' ]);
In your controller you can get your "environment" variables like this:
angular.module('myApp')
.controller('MainCtrl', function ($scope, $http, ENV) { // ENV is injected
$scope.login = function() {
$http.post(
ENV.apiEndPoint, // Our environmental var :)
$scope.yourData
).success(function() {
console.log('Cows');
});
};
});
With this approach you can easily automate the entire deployment pipeline. You can have a CI server push your changes to the appropriate servers and build the correct version of your app.
Here's a very helpful resource for you to read and from which I took the code samples: http://mindthecode.com/how-to-use-environment-variables-in-your-angular-application
Hope this helps you!
If you can key off the client hostname of the production versus the development servers ( or not the production server), then you can inject an $http interceptor to rewrite request URLs. In my app.js, I have something like:
var DEV_SRVR = 'http://my-machine.example.com:8000';
var PRODUCTION_FRONT_END_CLIENT_SUFFIX = 'DEPLOYMENT';
app.factory('REST_Interceptor',[
function() {
var request = function(config) {
if (RegExp(PRODUCTION_FRONT_END_CLIENT_SUFFIX,'i').test(window.location.host)) {
return config;
}
var rest_request_regex = new RegExp('^.*?/rest/(.*)$');
config.url = config.url.replace(rest_request_regex, DEV_SRVR+'/$1');
return config;
}
return {
request: request
}
}])
app.config([
'$httpProvider',
function($httpProvider) {
$httpProvider.interceptors.push('REST_Interceptor');
}])
Thus, requests from AngularJS like:
$http.get('/rest/foo/')
Go to http://my-machine.example.com:8000/foo/ if the client is not the production client interface, otherwise it goes to /rest/foo/ of the production host.
You might be interested in looking how David Ebbo and I setup this scenario. In David's talk at Build he discusses how to configure two websites to communication with each other. The source code for his talk is available on GitHub called ToDoApp. If you prefer to read over watch, there was also an article written about David's talk which is available in the Azure Documentation called Deploy a Complex Application predictably in Azure
You can set App Settings for you site and slot from the Azure Portal, and set separate settings for the dev and production slots.
See these docs for more details: https://azure.microsoft.com/en-us/documentation/articles/web-sites-staged-publishing/#configuration-for-deployment-slots
In my angularjs 1.3 app, i disabled debug info to increase performance :
$compileProvider.debugInfoEnabled(false);
But when i launch my Jasmine tests with Karma, i got error with isolateScope :
var isoScope = element.isolateScope();
I know that it is completely normal,
but i'm looking for a way to reactivate the debug just for testing.
Can i do it programmatically ?
Can i define that in the karma-unit.conf.js ?
My approach to this was to create a set of config files, the load specific files via grunt or gulp, depending on the task or environment variables.
example config:
// configFileLocal.js
angular.module('myapp.config')
.constant('apiUrl', 'https://myapi.com/api')
.constant('debugInfoState', true);
an excerpt from Gruntfile:
copy: {
test: {
src: (function () {
var filename;
if (process.env.REMOTE_TESTS) {
filename = './config-files/configFileTestsRemote.js';
} else {
filename = './config-files/configFileTestsLocal.js';
}
return filename;
})(),
dest: '<%= yeoman.app %>/scripts/root/config.js',
},
dev: {
src: './config-files/configFileLocal.js',
dest: '<%= yeoman.app %>/scripts/root/config.js',
},
...
then of course you need to load it in your app like this:
angular.module('myapp')
.config(function ($compileProvider, debugInfoState) {
$compileProvider.debugInfoEnabled(debugInfoState);
});
so /app/scripts/root/config.js is overwritten every time.
in index.html, the only file that is loaded is /app/scripts/root/config.js
this task should be run before any concatenation tasks.
Answer from wiherek is good but it isn't necessary to use your grun/gulp to manage this. Just transform snippet from Angular documentation to something like this
beforeEach(function() {
module('myApp', function (_$compileProvider_) {
_$compileProvider_.debugInfoEnabled(true);
});
});
Here's how I do it:
$compileProvider.debugInfoEnabled(!!window.__karma__);
To debug an application after disabling debugInfo, you should open your console and run this code directly from your console:
angular.reloadWithDebugInfo();
This will reload your page with all debugging info.
I need my app internationalized and have setting grunt-locales. Simply by adding the data-localize directive the text inside should be translated. My initConfig is configured as such-
locales: {
options: {
locales: ['en_US', 'de_DE']
},
update: {
src: 'app/views/*.html',
dest: 'app/js/locales/{locale}/i18n.json'
},
build: {
src: 'app/js/locales/**/i18n.json',
dest: 'app/js/locales/{locale}/i18n.js'
},
'export': {
src: 'app/js/locales/**/i18n.json',
dest: 'app/js/locales/{locale}/i18n.csv'
},
'import': {
src: 'app/js/locales/**/i18n.csv',
dest: 'app/js/locales/{locale}/i18n.json'
}
},
And data-localized is used in one case in the app here-
<a href="#" data-localize>Forgot your username or password?</a>
What that's done when grunt locales are built is create this file under app/js/locales/de_DE/i18n.json
{
"Forgot your username or password?": "Forgot your username or password?"
}
Which I don't have to tell you is not translated. So will I need to go through and translate this for every language or am I missing something?
Yes, grunt-locales is a tool used for extracting the necessary information from your templates and js files to build the above json objects for all locales that the developer specifies. You will need a translator to go through and translate the strings within the json objects.
Once they have done so, you simply re-build the i18n.json files to create i18n.js files using grunt locales:build. The i18n.js files are used by the localize directive service and filters.
Very late answer, but if you are still interested I hope this helps.