Yii2: Registering Asset Bundle vs registering external Js file - javascript

Hi I wanted to know the advantage of registering Asset Bundle following the process described in the docs like
Process one
in AppAsset.php
public $js = [
'js/myjsfile.js'
];
then in the view file
adding Namespace like
namespace app\assets;
and then adding the use statement like
use app\assets\AppAsset;
AppAsset::register($this);
Instead of doing all this if I use
Process Two
$this->registerJs('js/myjsfile.js', $this::POS_READY);
it works fine.
So why should I use Process One.
Any advantage and reason for this will be greatly appreciated.
If I follow the process one Do I need to add all the js files in
AppAsset.php individually.
Thanks.

Asset Bundles have some advantages over normal registering. Apart from what #deacs said in his/her answer here are others:
Assets Bundles can publish the file to assets if its not in web accessible directory
Assets Bundle can deal with less files (in case of CSS) as well as compressing the assets.
Makes Code Elegant especially in solving dependencies and hence reusability
All the features that makes bundles shine are found in docs

One of the main reasons for using an Asset Bundle is that your assets' paths will always be correct. Consider:
$this->registerJsFile('js/myjsfile.js', ['position'=>$this::POS_READY]);
will generate something like:
<script src="js/myjsfile.js"></script>
Which works great for non urlManager enabled urls, e.g. http://localhost/yiiproject/index.php?r=user/update&id=8 because your browser looks for the js file at: /yiiproject/js/myjsfile.js
But if you enable urlManager, your url will look like http://localhost/yiiproject/user/update/8, which means your browser will look for your js file at: /yiiproject/user/update/8/js/myjsfile.js.
You could overcome this problem by using:
$this->registerJsFile(Yii::$app->request->baseUrl.'/js/myjsfile.js', ['position'=>$this::POS_READY]);
But the Asset Bundle basicly does that for you.

Using Asset Bundles, you can also get the latest version from 'vendor' folder, so if you need to update some lib you don't need to manually do this since composer already do this.

Related

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

api.add_files is missing a file in Meteor package

I'm adding three files for the client in my Meteor package like so:
api.add_files([
'lib/client/newsletter_banner.html',
'lib/client/newsletter_banner.css',
'lib/client/templates.js'
], ['client']);
newsletter_banner.html defines a template which is not available when I load the site. If I look through the sources in Devtools, I can see that the CSS and JS files are available, but the HTML file is not. Why is this? I've confirmed that the filename is correct and even changed it thinking that name might be unavailable to me for whatever reason, but the file is still not included.
Html files are loaded by the templating package, so you need to add it to your package as well:
api.use(['templating', 'spacebars', 'ui'], 'client');

How to include minified library version in RequireJS optimization

My situation is as follows:
My project based on RequireJS.
I am using RequireJS Optimizer for to create a single JS file.
Some of the module use a certain third party library as a dependency.
The third party is NOT included in the optimized file (libName: empty
in the build config).
RequireJS is configured through var require = {} object which appears
on EACH PAGE, right above the RequireJS. The object defines a path to
the unminifed version of the library, among other things.
What i'd like to achieve:
Use the same config file in both development and production (the require={} object is included with tag on each page). During development I'd like modules to use the UNMINIFIED version of the third party.However, after optimization occurs, i would like all the modules to use the minified version of that third party.
I did think of a solution in theory, but it seems a bit messy and Im hopeful cleaner solution exists:
To have the runtime config point to unminified version
var require = {
paths:{
'thirdParty':'lib/thirdParty'
}
}
Create a module which execute (lets call it "PathRewrite" Module):
requirejs.config({
paths:{
'thirdParty':'lib/thirdParty.min'
}
})
In runtime configuration, define path to "PathRewrite" as empty
var require = {
paths:{
'thirdParty':'lib/thirdParty',
'PathRewrite':'empty'
}
}
In the build configuration file define a real Path to "PathRewrite" in order for it to be included in the "main" file (concatenated file after build).
Include "PathRewrite" as a dependency of a module which is executed first.
What I hope that will happen is that during dev, when optimized file is not used, PathRewrite is will not be used, hence the path to unminified third party in the runtime config will be used.
When the project is optimized, PathRewrite will be included and executed. According to RequireJS documentation, it is possible to run RequireJS configuration twice and the configuration will be appended/overwritten. PathRewrite execution will overwrite the path to "thirdParty" to minified, which will thus be used by all the modules.
Hopefully i've provided enough information. I'd be glad hear of other ways to get this done. Thanks in advance.
This topic appears to have been explored a bit in this answer:
Loading min.js files Generated by TypeScript with Require
Don't let the title discourage you. Typescript is not the core issue of the question being answered there. Unfortunately, the discussion reveals that the RequireJS optimizer may be the only way to get decent minification to work, as it seems incapable of selecting alternate paths properly.
Why don't you want to use inbuilt RequireJs optimizer? You may just include this option
optimize : "uglify2"
and all your and third-party code will be minified after concatenation. In this case you don't need to use minified versions of third-party libraries.

how to minify js files in order via grunt-contrib-uglify?

I have a directory like below:
/folder/b.js
/folder/jQuery.js
/folder/a.js
/folder/sub/c.js
I want to minify all these js files in one js file in order:
jQuery.js -> a.js -> b.js -> c.js
Q:
1.How can I do it via grunt-contrib-uglify?(In fact, there are lots of files, it is impractical to specify all source filepaths individually)
2.btw, How can I get unminified files when debug and get minified single file when release and no need to change script tag in html(and how to write the script tag)?
Good questions!
1) Uglify will reorder the functions in the destination file so that function definitions are on top and function execution on bottom but it seems that it will preserve the order of the function executions.
This means that the function jQuery runs to define its global functions will be put first if you make sure jQuery is mentioned first in Uglify's config in the Gruntfile.
I use this config:
uglify: {
options: {
sourceMap: true
},
build: {
files: {
'public/all.min.js': ['public/js/vendor/jquery-1.10.2.min.js', 'public/js/*.js'],
}
}
}
2) I don't think there is one definite way to accomplish this. It depends on what web framework, templating framework and what kind of requirements you have. I use express + jade and in my main jade layout I have:
if process.env.NODE_ENV === 'production'
script(src='/all.min.js')
else
script(src='/js/vendor/jquery-1.10.2.min.js')
script(src='/js/someScript.js')
script(src='/js/otherScript.js')
In my package.json I have:
"scripts": {
"postinstall": "grunt"
},
This means that when I run npm install on deploy (on Heroku) grunt is run to minify/concat files and when the app is started with NODE_ENV=production the minified client side javascript is used. Locally I get served the original client side javascripts for easy debugging.
The two downsides are:
I have to keep the two lists of script files in sync (in the Gruntfile and in the layout.js) I solve this by using *.js in the Gruntfile but this may not suite everyone. You could put the list of javascripts in the Gruntfile and create a jade-template from this but it seems overkill for most projects.
If you don't trust your Grunt config you basically have to test running the application using NODE_ENV=production locally to verify that the minification worked the way you intended.
This can be done using the following Grunt tasks:
https://github.com/gruntjs/grunt-contrib-concat concatenates
files
https://github.com/gruntjs/grunt-contrib-uglify minifies
concatenated files
EDIT
I usually run all my files through a Grunt concatenation task using grunt-contrib-concat. Then I have another task to uglify the concatenated file using grunt-contrib-uglify.
You're probably not going to like this, but the best way is to define your js source files as AMD modules and use Requirejs to manage the order in which they load. The grunt-contrib-requirejs task will recurse your dependency tree and concatenate the js files in the necessary order into one big js file. You will then use uglify (actually r.js has uglify built-in) to minify the big file.
https://github.com/danheberden/yeoman-generator-requirejs has a good example gruntfile and template js files to work from.
EDIT
I've recently started using CommonJS modules instead of AMD since it's much closer to the ES6 module spec. You can achieve the same results (1 big complied+concatenated js file) by running commonjs modules through Browserify. There are plugins for both grunt and gulp to manage the task for you.
EDIT
I'd like to add that if your site is written using ES6 that Rollup is the best new concatenating package. In addition to bundling your files, it will also perform tree shaking, removing parts of libraries you use if included via an import statement. This reduces your codebase to just what you need without the bloat of code you'll never use.
I don't think you can do this with the uglify task alone, but you have a multitude of choices which might lead to your desired outcome.
A possible workflow would be first concatenating (grunt-contrib-concat) the files in order into one single file, and put this concatenated file through uglify. You can either define the order for concat in your Gruntfile, or you use on of those plugins:
First one would be https://github.com/yeoman/grunt-usemin, where you can specify the order in your HTML file, put some comments around your script block. The Google guys made it and it's pretty sweet to use.
Second one would be https://github.com/trek/grunt-neuter, where you can define some dependencies with require, but without the bulk of require.js. It requires changes in your JS code, so might not like it. I'd go with option one.
I ran into the same issue. A quick fix is just to change the filenames - I used 1.jquery.min.js, 2.bootstrap.min.js, etc.
This might be only remotely related to your question but I wanted something similar. Only my order was important in the following way:
I was loading all vendor files (angular, jquery, and their respective related plugins) with a wildcard (['vendor/**/*.js']). But some plugins had names that made them load before angular and jquery. A solution is to manually load them first.
['vendor/angular.js', 'vendor/jquery.js', 'vendor/**/*.js]
Luckily angular and jquery handle being loaded twice well enough. Edit: Although it's not really the best practice to load such large libraries twice, causing your minified file unnecessary bloat. (thanks #Kano for pointing this out!)
Another issue was client-js the order was important in a way that it required the main app file to be loaded last, after all its dependencies have been loaded. Solution to that was to exclude and then include:
['app/**/*.js', '!app/app.js', 'app/app.js']
This prevents app.js from being loaded along with all the other files, and only then includes it at the end.
Looks like the second part of your question is still unanswered. But let me try one by one.
Firstly you can join and uglify a large number of js files into one as explained by the concat answer earlier. It should also be possible to use https://github.com/gruntjs/grunt-contrib-uglify because it does seem to have wildcards. You may have to experiment with 'expand = true' option and wildcards. That takes care of your first question.
For the second part, say you joined and uglified into big-ugly.js
Now in your html you can add following directives:
<!-- build:js:dist big-ugly.js -->
<script src="js1.js"></script>
<script src="js2.js"></script>
<!-- etc etc -->
<script src="js100.js"></script>
<!-- /build -->
And then pass it through the grunt html preprocessor at https://www.npmjs.com/package/grunt-processhtml as part of your grunt jobs.
This preprocessor will replace the entire block with
<script src="big-ugly.js"></script>
Which means that the html file with be semantically equivalent - before and after the grunt jobs; i.e. if the page works correctly in the native form (for debugging) - then the transformed page must work correctly after the grunt - without requiring you to manually change any tags.
This was #1469's answer but he didn't make it clear why this works. Use concat to put all js files into one, this module does this in the order of file names, so I put a prefix to the file names based on orders. I believe it even has other options for ordering.
concat: {
js: {
options: {
block: true,
line: true,
stripBanners: true
},
files: {
'library/dist/js/scripts.js' : 'library/js/*.js',
}
}
},
Then use uglify to create the minified ugly version:
uglify: {
dist: {
files: {
'library/dist/js/scripts.min.js': [
'library/js/scripts.js'
]
},
options: {
}
}
},
If your problem was that you had vendors which needed to be loaded in order (let's say jquery before any jquery plugins). I solved it by putting jquery in its own folder called '!jquery', effectively putting it on top of the stack.
Then I just used concat as you normally would:
concat: {
options: {
separator: ';',
},
build: {
files: [
{
src: ['js/vendor/**/*.js', 'js/main.min.js'],
dest: 'js/global.min.js'
}
]
}
},

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