How to organize App in durandal? - javascript

I have created a simple durandal SPA based on tutorial from #john_papa, in this video from plural sight, he installs the nuget package but the video is already like one year old.
In that version, when the durandal package is installed it would create an App folder, and then a durandal folder with many js files there.
In the new version of durandal, there is no App folder created and instead all durandal files are created under /scripts/folder.
Based on the tutorial I created my structure like this:
http://screencast.com/t/13B4YhqExVRQ
However when I run it I got on F12 developer tools this error:
http://screencast.com/t/Sfdd0kLK
I know the path is different to the tutorial, thats why I ask how should I organize and how should I use the define method or function.
I tried
define(['Scripts/durandal/system', 'logger'],
but that didnt work
I noticed my main.js has:
require.config({
paths: { "text": "durandal/amd/text" }
});
define(function (require) {
var system = require('durandal/system');
app = require('durandal/app');
system.debug(true);
app().start().then(function () {
app.setRoot('shell');
});
});

Your question is quite opinionated in that project structure can vary greatly depending on who you ask. Given that I will give you my quick opinion -
Anything that you do not plan to modify should be in your scripts or vendor folder. In the project structure you are referencing I would have my Durandal and related scripts in there.
Anything that you plan to modify should be separate. If that means placing an App folder at root then do that. I would suggest keeping it all under one directory though (such as App) so that by convention you can set up all of your routes and other application code.
I always use a convention of separating views and view models as Durandal 2.x suggests (root/app/views // root/app/viewmodels) and have a well defined structure from there such as having a home directory on each side.
As far as why it isn't working currently you need to point your require.js config in main.js to the correct directory that Durandal lives in.
I would add this to your config -
require.config({
paths: {
'text': 'durandal/amd/text',
'durandal': '../Scripts/durandal',
'plugins': '../Scripts/durandal/plugins',
'transitions': '../Scripts/durandal/transitions' }
});

This document helped me to find the problem.
http://durandaljs.com/documentation/Conversion-Guide.html

Related

Rails-like autoload in javascript - Allow dependency to require from root package in webpack

I am writing a framework package which I'd like to make it able to auto require modules from the main projects src/. If you are familiar with rails, this is akin to its autoload feature.
So if in your web app you follow a directory convention, say src/models/my-model.js, then the framework can require the my-model module on its own. The framework, which is a dependency of the web app, only need know the name of the relation (ie "todos") in order to require the model (ie. src/models/todo.js)
I've tried adding my web apps src directory in my web apps webpack chain config.resolve.modules.add(path.resolve(__dirname, 'src')) but it does not seem to apply to the search paths for dependencies (not sure) so my framework lib still can't find modules in my web app.
I've also (desperately) tried passing require from the web app to the dependency and then in the dependency code I call var MyModel = this.thePassedInRequireFn("./models/" + modelName), but it errors:
(`Uncaught Error: Cannot find module './models/my-model'
at MyFramework.webpackEmptyContext
Anybody have ideas how this can be done?
If the solution can be independent of the use of webpack, that would be ideal, but webpack compatibility is what is most important to me.
Here is a webpack specific answer using require.context().
https://webpack.js.org/guides/dependency-management/#require-context
In the web app create a require context. For example:
const requireModule = require.context('./models/', true);
Then pass requireModule to the framework you have as a dependency of your web app.
If your web app has a model in the file ./models/todo-item.js and the framework is given the models name todoItem, the framework can require it using only the model name like so:
let fileName = `./${kebabCase(modelName)}`;
let module = this.requireModule(fileName).default;

How can I create a shared utility to use with two different AngularJS apps (on different html pages)?

I'm working on a multi-page site using AngularJS, and I want to write a utility that can be included in more than one page. I've looked at services and providers, and all the examples I find are single-page examples. I'm not sure how to generalize this to multiple apps used on different pages.
This is what I want to have for my two different pages/apps.
in app1.js:
var app1 = angular.module('app1',['myUtil'])
app1.controller('ctrl1',function ctrl1($scope,myUtil){...})
in app2.js:
var app2 = angular.module('app2',['myUtil'])
app2.controller('ctrl2',function ctrl2($scope,myUtil){...})
in myUtil.js:
??? Provider? Service? Module?
All the examples I have found for providers and services show them as being attached to a single app. Is this possible with AngularJS? If so, what am I missing?
The answer from zero298 is a nice answer as it's a great way of organising and reusing the utility module you create.
If you want a less broad and more "codey" answer, then one way of doing it would be to have some kind of utility module that houses whatever services you want to put in it, and then you can pass that in as a dependency for all apps that use it. This will all depend on your build process as to how you import/organise the files, but as a very basic example you could have a "utilsmodule" module with a "utils" service:
myUtils.js:
angular.module('utilsmodule', []);
// Service could be in another file
angular.module('utilsmodule').service('myutil', function() {
return {
myUtilFunction : function() {
return "This is from myutil";
}
};
});
Then in your app files you can pass in the module by name, which will give the app access to the 'myutil' service.
app1.js:
var app1 = angular.module('app1',['utilsmodule'])
app1.controller('ctrl1',function ctrl1($scope,myutil){...})
Then you would import the myUtils.js file before the app1.js file so that the "utilsmodule" module is registered with angular before your app is created. You do the same with app2 and the utility module should be available to both.
Example Plunker
This may be a bit too broad. However, what I would suggest you do is create a library module dedicated to the feature/utility that you want to make available to your projects.
I would suggest using npm to organize all of this. Give this feature module it's own package.json and add whatever code you need to make it run. In your consumer projects, add the library module as a dependency.
A good method to get this working locally (as well as quickly since you don't have to constantly push to the npm registry) is to use the npm link utility.
If your consumer projects are already npm oriented, the workflow would be as follows:
Create a new directory to contain your utility library module lets call it my-utility
cd to the new directory
npm init to create a package.json in this library
npm link to make the library available locally
cd to any of the consumer projects
npm link my-utility so that the consumer projects create a symlink to the local folder that contains your utility module
After that is setup, depending on how your consumer projects build or use dependencies, you can use your new utility library in your top level projects.

Webpack import angular in angular-webpack-workflow demo project

I'm inspecting this Webpack demo project https://github.com/Foxandxss/GermanWords-ng1-webpack. This project contains in several files this line (features/home/index, features/login/index):
import angular from 'angular';
I don't understand - if 'angular' library will be included several times in result js file because this library exists in several source files? I've looked at webpack config file, but can't understand. Please, can you clear this questions?
It's just to be sure that angular will be available for every module. Imagine you use a feature to be a new standalone site, you will have already the code ready. Always think like every feature is a standalone (handle routes, dependencies, controllers, views etc.).
Webpack will handle the dependencies and inject only once angular, dont worry. Like #thsorens says in comments : "All dependencies in your files needs to be imported where it is used."
Oh and for furthermore, i've found this yeoman generator base on Foxandxss work : https://github.com/Aleksion/generator-angular-webpack-babel
Have fun.

Bundling and Minifying Durandal Applications

I'm using MVC5/Durandal and wondering what the recommended approach to bundling/minifying a durandal application would be. Ive seen docs on using Weyland but will be deploying to an Azure Website and don't see how to leverage this in my .net-based deployment process. How can I go about configuring automated bundling/minification of my durandal application when deploying to Azure?
I've spent a bit of time trying to optimize an AngularJS application for one of the biggest banks in Holland. Although it's no Durandal, this might still give you some ideas.
So what did we use for bundling and minification? Out-of-the-box bundling and minifcation from ASP.NET MVC ( which is from the system.web.optimization namespace )
You need to get a couple of things in order to leverage this:
Organize your files
Organize your code files in a way that they can easily be bundled. We had a large tree structure under the /app folder in the web project. Something like:
- App
|- Modules
| |-Common
| | |- Directives
| | |- Templates
| | |- Filters
| --User
| ...
| app.js
So the application skeleton was inside the app.js and all the other JS files were required by the application. Point being: all SPA code is separated from vendor javscript files and the rest of course
Set up the budling inside the bundle configuration
That's a breeze now, just do regular-old-bundling from your Global.asax.cs:
Make sure there's a line in the Application_Start() with:
BundleConfig.RegisterBundles(BundleTable.Bundles);
That calls into your BundleConfig class which only needs 1 bundle to pack up the whole /app folder:
bundles.Add(new ScriptBundle("~/bundles/app")
.Include("~/app/*.js")
.IncludeDirectory("~/app", "*.js", true));
We needed the app.js to load first - therefore we put it explicitly at the top. Don't worry, it will not be requested twice.
For bundling - only the sequence of files can be important. However, through including that file explicitly, we could control that and it worked like a charm.
Minification
Now for minification we had to do some code changes. AngularJS can be used with different types of syntax - some of which can be minified, others give problems.
Example:
angular.module('myapp').controller(function($http,$scope) { ... });
can not be minified. The minifyer will change the name of $http so something shorter, after which the injector cannot do dependency injection anymore, since it only knows stuff called $http and $scope and not the minified variable name.
So for Angular you need to use a different syntax:
angular.module('myapp').controller(['$http', '$scope', function($http,$scope) { ... }]);
With this, the injector will know that the first argument of the function is '$http' because that's the first string variable in the array. OK, but that's Angular and you're looking for Durandal.
I've heard that Durandal uses AMD right? So within a module, minification shouldn't be a problem, because it should be smart enough. However, if you're using external things, you want to make sure everything still works. I've read here that you'll want to use te following syntax for your AMDs:
define("someModule", ["jquery", "ko"], function($,ko) { ... });
And that gave us a reduction of 80% of the requests and around the same number for the Javascript payload.
Added AngularJS bonus
This might not be of interest to you, but maybe for other readers. The reason we didn't get a 99% reduction of requests is because AngularJS uses something called 'directives'. These are like HTML templates. Those HTML templates still needed to be downloaded every time they were used.
They were also included in our /app folder - hence we had to add an IgnoreRoute in the routeconfig:
routes.IgnoreRoute("app/");
I Googled, but couldn't find anything similair for Durandal. So Angular will go and get all of the small HTML files, but will first check its $templatecache. In case the HTML content is not in the cache, it goes out and downloads it and places it in the cache, so it needs to be downloaded only once.
We, well I, wrote a T4 generator that outputs a JS file in which all the HTML files in the /app folder are added to the $templatecache. So the output would look like:
angular.module('myapp').run(function($templateCache) {
/// For all *.html files in the /app folder and its children
$templateCache.put('...filename...', '...content of html file ...');
});
Because this .JS file was inside the /app folder, it would immediately get bundled with the application, no more configuration required. This got our requests down for the whole application to just 1. Since the amount of HTML was quite small, it seemed to be faster to do 1 larger request, then multiple smaller ones.
Point is: if Durandal has something similair and it will look for some templates, find the caching mechanism ( because it will have it ) and try to tap into that.
Controlling bundling and minification
I'll quote this site: http://www.asp.net/mvc/overview/performance/bundling-and-minification
Bundling and minification is enabled or disabled by setting the value
of the debug attribute in the compilation Element in the Web.config
file. In the following XML, debug is set to true so bundling and
minification is disabled.
<system.web>
<compilation debug="true" />
</system.web>
So for your release build - this flag shouldn't be set and thus bundling + minification should happen.
But of course, you will want to test locally - you can either remove this from your web.config or override it with BundleTable.EnableOptimizations = true;
Deployment to Azure
Then you mention deployment to Azure. I don't know how this would be any different from deploying to a local server. We used web-deploy. Bundling and minification doesn't happen build-time, so there are no changes in the build process. Also, the optimization framework is being deployed with the site - so no difficult things for deployment either.
Maybe one thing though: you could consider adding the CDN for the libraries you are using:
bundles.Add(new ScriptBundle("~/bundles/jquery", "http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.1.min.js")
In case the CDN location of jQuery was already cached by the client's browser, you'll save another request.
Measuring the performance was easy: just open up the network tab on Chrome and reload the page ( make sure it's not caching ). Check the total number of requests and the total amount of data downloaded. Like I said: we saw a huge improvement.
Well, hope it helps or points you in a right direction.
The below answers are pretty complicated. I've just gone through this with a simple(r) approach here:
https://lifelibertycode.wordpress.com/2015/04/14/how-to-bundle-up-your-mvc-durandal-app/
The steps below:
Step 1: Install Node
Step 2: Install Gulp
$ npm install --global gulp
$ npm install --save-dev gulp
Step 3: Create your gulpfile.js
This should be at the root of your project, and should initially contain this:
var gulp = require('gulp');
gulp.task('default', function() {
// place code for your default task here
});
Step 4: Install gulp-durandal
npm install gulp-durandal --save-dev
Step 5: Update your gulpfile.js
var durandal = require('gulp-durandal');
gulp.task('durandal', function(){
durandal({
baseDir: 'app', //same as default, so not really required.
main: 'main.js', //same as default, so not really required.
output: 'main.js', //same as default, so not really required.
almond: true,
minify: true
})
.pipe(gulp.dest('dir/to/save/the/output'));
});
Step 6: Add a post-build event to your project
if '$(Configuration)'=='Release' (
cd $(ProjectDir)
gulp durandal
)
Step 7: Add a pre-build event to your project
I needed this because occasionally gulp would hang when generating the new main-built.js on top of an existing version. So I just delete the old version before the build begins:
if '$(Configuration)'=='Release' (
cd $(ProjectDir)/app
del main-built.js
del main-built.js.map
)
Now, when you build your project, you’ll generate a new main-built.js file each time that can be served down to your clients. Sweet.
At this point, you probably have some concerns.
How do I keep my files un-bundled when I’m debugging?
#if (HttpContext.Current.IsDebuggingEnabled) {
<script type="text/javascript" src="~/Scripts/require.js" data-main="/App/main"></script>
} else {
#Scripts.Render("~/Scripts/main-built")
}
Where ‘main-built’ is defined in your BundleConfig:
bundles.Add(new ScriptBundle("~/Scripts/main-built").Include(
"~/app/main-built.js"));
How do I bust cache when I have new stuff to ship?
If you’re using the above approach, bundling will take care of this for you. ASP.NET will detect a change to your main-built.js file and append a unique identifier to your bundles to bust the cache.
What if my client has downloaded my SPA, and then I ship an update. Won’t the (outdated) client-side code stick around until they refresh?
Yup. Unless you leverage build versioning to tell the client when it’s out of date, and then tell the user.
I happen have written a blog post about this:
https://javascriptkicks.com/articles/4230
Hopefully that helps you out

How to structure a JavaScript project?

I'm currently working on a big JavaScript project for which we want to define our own API. I'm using RequireJS as my dependency loader and it suits me just fine, allowing me to define modules in their respective file. I do not make use of my own namespace, a module returns an instance, which can be used in other modules, i.e.:
define(
['imported_module'],
function(module){
module.doSomething();
}
)
However as the number of files grows, I'd like to decide how to structure these files in folders. Currently I use the following scheme to name my files:
[projectname].[packagename].[ModuleName]
An example could be stackoverflow.util.HashMap.js. I would like to introduce a project folder, a folder per package and rename the files to the module name, like:
stackoverflow/util/HashMap.js
This structures my code quite neatly into folders, however the filename reflects only the module now. I'd like to define some kind of routing to be able to define how RequireJS should look for files. Example:
The file
stackoverflow/util/stackoverflow.util.HashMap.js
Should be importable by the statement
define(['stackoverflow.util.HashMap'],function(HashMap){});
Has anyone experience with structuring large JavaScript projects and if so, could you share your approach?
You shouldn't specify the routing info on your js file names, those are the namespace and folder paths' jobs. So stackoverflow/util/HashMap.js is just fine. And you can use define("stackoverflow/util/HashMap", ....) to tell the dependency.
If you need to put your modules in a different folders, you can config paths for your loader, see this manual from RequireJS API.
There's no best way for structure your js files. But put the root namespace in a src folder is always a good practice. You can see the dojo source code and YUI source code and use similar ways for your project. They both are large scale Javascript projects.
actually it's better to get js lib routing to load all js using standard interface: "js.yoursite.com/lib-0.2.js" there should be a router (php or other, and able to cache queries). So there you could determine and control whole pathes that you use. Because common jquery plugin should stay at one dir, with jquery, and your own custom plugins not.
And there you control each project by it's own rules:
jquery/
plugins/
jquery.prettyPhoto.js
jquery.min.js
mySuperJS/
stable.0/ -- there your production version for 1.0 branch
module.js
0.1/
module.js
0.2/
module.js
0.3/
module.js
myOtherlib/
stable.0/ -- production version for all 0.* versions
stable.1/ -- production version for all 1.0 versions
0.1/
0.2/
0.3/
0.4/
0.4.1/
0.4.1.18/
We're using such structure around a year and it's the best for us. But sometimes we use more complex solution and separate all modules for libs, plugins, tools, components and apps.

Categories