To bundle and minify the files of my application I'm using .NET Bundling and Minification feature (and it's complicated to use any other tool, so I would like to find a solution for this).
As a remark and just in case it could help, I'm trying to minify angular files, files compiled from typescript and regular javascript files.
The problem is that when I execute the application I got some javascript exceptions related mostly to angular. I think it's not useful but maybe it could help, so this is the main exception I get:
Error: [$injector:unpr] Unknown provider: tProvider <- t <- highChartSeriesMappingService
http://errors.angularjs.org/1.5.5/$injector/unpr?p0=tProvider%20%3C-%20t%20%3C-%20highChartSeriesMappingService
[Rest of the exception trace][...]
If I just bundle them and no minify my files, by setting this instruction myScriptBundle.Transforms.Clear(), it works fine. Obviously, this action also avoids the mangling of variables, classes...names.
In order to be sure of what was happening, I used grunt and its plugin grunt-contrib-uglify to bundle and minify. At the beginning I had the same problem as before because of the basic configuration I used for the task:
uglify: {
options: {
preserveComments: false
},
myScript: {
files: {
'myScript.min.js': conf.myScript
}
}
}
Where conf is a reference to a json file with the url of the files to minify. But when I set the mangle property to false (here you have more info about it):
uglify: {
options: {
preserveComments: false,
mangle: false
},
myScript: {
files: {
'myScript.min.js': conf.myScript
}
}
}
It works fine too. It leads me to think that is the mangling of some classes names the root of the problem.
As you can see, I reached a solution using grunt but I would like to find a way to avoid mangling with .NET. Any idea?
After read the post of #AbdelrhmanMohamed and check again my project's files I realized that it was a problem related to angular and its array sytntax; some files lacked the dependencies injection through the array strings, so when those files where minimizied and the functions' names where mangled caused my exception in execution time.
To sum up, .NET bndling and minification process seems to work fine. If you work with angular you just have to be sure that all your classes which depend of other modules have the array string with those dependencies defined.
Related
I have several js modules that I bundle with browserify in gulp:
gulp.task('build:js', ['clean:js'], function () {
browserify({
debug: true,
entries: paths.js.src
})
.transform('babelify', { presets: ['es2015'] })
.bundle()
.pipe(source('bundle.js'))
.pipe(buffer())
.pipe(gulp.dest(paths.js.dist));
});
It outputs a single bundle.js. However, when bundled like this I can't require individual modules in the browser. Which I'd like to do, because I don't want to always initiate every module (some are page specific). Instead of that I'd like to be able to use var someModule = require('some-module'); on a page alongside the bundle.
Now I couldn't really find anything about this in the documentation, since it only documents the commandline options (and not the js api). This answer shows that a module can be required and exposed from gulp, but that would expose my entire bundle, and not the modules of which it is composed.
A solution would be to bundle all my modules separately, exclude dependencies (so they won't be duplicated across bundles) and then concatenate that. But that doesn't really seem like a viable solution because:
The modules might have trouble resolving dependencies since everything is bundled separately, and thus dependencies would have to be resolved in-browser. Not ideal and prone to breakage I think.
It is very labour intensive since I use a lot of modules, and each will have to be exported manually in gulp, dependencies excluded and referenced in my templates. There are ways to automate it, but this doesn't exclude shared dependencies.
So how do I solve this? How can I require the bundles of which my js is composed separately in the browser, for client side use?
So what I ended up doing is something else. What I was asking kind of went against the way that browserify works, even though it's possible. Maybe eventually when HTTP2 imports and js modules can be used in all major browsers this'll be easier.
For now, I just have a global bundle for the scripts that run on every page, including all my third party libraries. And then for each page I have an separate entry point with the local modules that it needs. This is so far the most maintainable solution.
I'm working on a large project, where minification of java and css was used before with Web Essentials, but no bundling has been yet made.
I want to move away from WebEssentials now and use something else.
I'v started with minification and bundling just a part of app .js files using Gulp, but when loading the project got an error from one of the pages:
Error: $injector:unpr Unknown Provider caused by other .min.js files,
which all worked fine before I've added mine.
All files are added to the path exactly in the same order, as they should be loaded on the page. Also replaced just those files which have been added to the bundle.
gulp code for minification:
gulp.task("min:appjs", function ()
{
return gulp.src(["!gulpfile.js", "!app/" + paths.minJs, paths.appjs], { base: "." })
.pipe(sourcemaps.init())
.pipe(concat(paths.concatJsAppDest), { newLine: ';' })
.pipe(ngmin())
.pipe(annotate())
.pipe(uglify())
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest('.'));
});
What I can't wrap my mind about: how my minified file causes others minified files to crush when they've worked before. Plus one page of the project loads without any errors and others don't work.
Any ideas where I should check?
Ok, so the way I had to handle this was checking which file caused the error.
It appeared so one of the .js.min files from further if statements in the code was referring to the file in the bundle and couldn't find the reference.
Taking that file out of the bundle and keeping it separately solved the problem.
If there are any ideas how to include it in the bundle and still keep the reference, I would be glad to try it out.
I have been researching the ability to minify an ExtJS application without Sencha and the closest I have come to is this link:
Is there a way to minify an ExtJS application without Sencha CMD?
However, I am not sure how to execute the file in one of the later comments. I am using the minify-maven-plugin with com.samaxes.maven and the CLOSURE engine. I was able to generate the minified js file of the entire project but I get errors when I try to load the web page.
I was able to verify the web page was calling the correct file. I received the error "TypeError: q is undefined"...not helpful at all. Without the minified file, the web application runs perfectly. So, the generated minified file must have something wrong with it.
The suggestion at the bottom of the link above indicates the sequence of files that I should include but I have no idea how to actually implement this. Also, there are probably over a hundred javascript files that need to be minified so I would rather not have to type each file in the jsb file.
Are there any suggestions on how to minify my entire project at build time with maven?
I'm using Grunt to build the project, but it doesn't really matter as all you need is to combine files, so maven should be more than capable.
I wanted my dev version to still rely on Extjs dynamic class loader so I don't have to rebuild the project whenever I modify one file, and only production version to be minified into a single file. There were a few pitfalls before I got it working, here is what I ended up with. Also this is for ExtJS6, but it probably still should be the same.
It is all controlled by backend variable dev, which when set to false will use minified sources.
index.html (I'm using some meta templating language as example)
<html>
<head>
{{if dev}}
<script src="/ext/ext-all-debug.js"></script>
{{else}}
<script src="/ext/ext-all.js"></script>
{{/if}}
<script>
var dev = {{dev}};
Ext.Loader.setConfig({enabled: dev});
</script>
{{if dev}}
<script src="/app.min.js"></script>
{{else}}
<script src="/app.js"></script>
{{/if}}
</head>
<body></body>
<html>
app files, requires directive doesn't work well when the dynamic loader is disabled, so I had to add conditions like this everywhere:
Ext.define('MyApp.view.Panel', {
extend: 'MyApp.view.GenericPanel',
requires: dev ? [
'MyApp.view.AnotherView',
] : [],
...
});
Gruntfile.js (if you need only concatenation replace uglify with concat everywhere)
module.exports = function(grunt) {
grunt.initConfig({
pkg : grunt.file.readJSON('package.json'),
uglify : {
build: {
files: {
'../app.min.js': ['../app/view/GenericPanel.js', '../app/**/*.js', '../app.js'],
}
}
},
});
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.registerTask('default', [ 'uglify' ]);
};
grunt's project.json:
{
"name": "My App",
"version": "1.0.0",
"devDependencies": {
"grunt": "~0.4.5",
"grunt-contrib-concat": "^1.0.1",
"grunt-contrib-uglify": "^1.0.1"
}
}
The order of files matter, by default grunt will use alphabetic order. If you extend some class, the parent class has to be included higher. app.js should be at the end. Other than that it is working well in a single mixed file, so I didn't have to customize the file order further. Grunt has pretty powerful path patterns, so if you need to make sure some file is included first you just list it before other path patterns and it will be smart enough to not include it twice.
I recommend you start with simple concatenation without minification, and only if that works try minifying it. When minifying you might need to be careful with global functions and variables as they can be renamed if minifier is too aggressive. Grunt's minifier almost worked for me with the default settings, I just had to made couple small changes to my code (related to global functions).
While I am not sure why you would want this, the main thing you need is the so-called dependency tree - which tells you the order in which to include the source files.
Then you can put all the files (ExtJS source, libraries if applicable and also your own views) into one big file, in the correct order. This file should then work exactly as the 500 distinct files. (It did for me.)
That done, you can search for a working minifier. Not every minifier can minify ExtJS code, and I don't remember my last results before we finally decided to switch to Sencha Cmd, but I think Microsoft Javascript Minifier was one that worked for us.
Apart from that, minified JavaScript is really legible. You should provide the source of the error, with 200 characters before and 200 characters after the error, and I guess someone here can tell what's going on there.
I've been learning Angular for awhile now and am looking into the best ways to modularize the application. I think my understanding of that is going pretty well, but I've looked around for awhile and can't seem to get a good answer as to how all of these .js files get included inside of the index.html without just manually typing out a bunch of tags. I've been looking into Grunt/Gulp and get how those are combining the entire app into one .js file, but for development I'm guessing you don't want to have to re-run grunt or gulp every time you want to update the app.
There are many different options: gulp, grunt, or webpack seem to be the most popular. I tend to use webpack the most these days.
A good setup will typically run a local node server, which will refresh the browser automatically every time you make a change to a file.
There are many yeoman generators that will set this all up for you, or you can find simple examples on github.
The basic idea is to
concatenate all your js files (in proper order so the module definitions go before controllers/services)
minify if for production
copy to a fixed path
include this single js file in your html
during development have your grunt/gulp script watch for changes in js files and re-run the above steps so you don't have to run the grunt/gulp task manually.
Now if you are using gulp here is how you would typically handle above steps
gulp.task('process-js', function () {
return gulp.src(jsFiles, {base: './'})
.pipe(gulpif(isProd, concat('all.min.js'), concat('all.js')))
.pipe(gulpif(isProd, uglify({mangle: true, preserveComments: 'some'})))
.pipe(gulp.dest(deployFolder + '/static/js'))
});
where jsFiles is an array of files/folders that contain your angular app js files
var jsFiles = [
'!static/js/**/*.js',
'!static/js/plugin/**/*.*',
'app/index/**/*module.js',
'app/index/**/*config.js',
'app/index/**/*ctrl.js'
];
Note: use ! prefix to exclude certain files/folders from processing.
isProd is just a flag that indicates whether you are preparing for production or development.
During development I also use BrowserSync to watch any changes to my js files and re-run the gulp task to prepare all.js. It also refreshes the page in browser automatically.
gulp.task('run', function () {
browserSync({
server: {
baseDir: deployFolder
},
open: true,
browser: ['google chrome'],
files: deployFolder + '/**/*',
watchOptions: {
debounceDelay: 2000
}
});
gulp.watch(jsFiles, ['process-js']);
});
gulp.task('default', function () {
runSequence(
'clean',
'run'
);
});
Gulp/Grunt to concat all your angular files.
Create 2 tasks :
a dev build task
concat to one file BUT don't uglify.
a distribution/production task which is the same as dev one but this one uglify the concatenated file.
When I write tests for my in-browser TS code, I hit the following problem. My "test" code files are located in a separate folder from the "application" code files (an arrangement that I am not willing to give up). Therefore, in order to import my "app" modules, I have to do this:
// tests/TS/SubComponent/Module.Test.ts
import m = module("../../Web/Scripts/SubComponent/Module");
This compiles just fine. But when loaded in browser, it will obviously not work, because from the standpoint of RequireJS running in the browser, the module is located at "app/SubComponent/Module" (after being remapped through web server and RequireJS config).
With TS 0.8.3 I was able to pull off this clever trick, but in 0.9.0 it no longer works, because now the compiler doesn't let me treat a module as an interface.
So the question is: how do you test your client-side code?
Clearly, I can't be the only person to be doing it, can I? :-)
I can't tell if you are using Visual Studio - this next bit is Visual Studio specific...
This is how I do it:
In my test project, I created a folder named "ReferencedScripts" and
referenced the scripts from the project being tested (add existing
item > add as link). Set the file to copy to the output folder.
Source: Include JavaScript and TypeScript tests in Visual Studio.
Using add-as-link makes the scripts available in your test project.
Not using Visual Studio? I recommend creating a task / job / batch file to copy the files into the test folder. You could even use tsc to do this task for you.
I am in the middle of a project where I have to migrate parts of a large javascript project to typescript and this is how I managed to keep the tests running:
Use grunt-typescript task to watch and compile all my .ts files from the source to a tmp folder (with their source-maps). If you only have to deal with typescript files, then you can use the tsc in watch mode to do it as well. The latter would be faster, but the former allowed me to simultaneous edit javascript and typescript files with livereload.
Include the .ts files in karma.conf but don't watch them or include them:
// list of files / patterns to load in the browser
files = [
JASMINE,
JASMINE_ADAPTER,
// ...
// We want the *.js to appear in in the window.__karma__.files list
{ pattern: 'app/**/*.ts', included: false, watched: false, served: true },
{ pattern: 'app/**/*.js', included: false },
// We do watch the folder where the typescript files are compiled
{ pattern: 'tmp/**/*.js', included: false },
// ...
// Finally, the test-main file.
'tests/test-main.js'
];
Finally, in the test-main.js file, I mangle the names of typescript files and declare them as require modules with the correct paths (to the corresponding .js file) in test-main.js:
var dynPaths = {
'jquery' : 'lib/jquery.min',
'text' : 'lib/text'
};
var baseUrl = 'base/app/',
compilePathUrl = '../tmp/';
Object.keys(window.__karma__.files)
.forEach(function (file) {
if ((/\.ts$/).test(file)) {
// For a typescript file, include compiled file's path
var fileName = file.match(/(.*)\.ts$/)[1].substr(1),
moduleName = fileName.substr(baseUrl.length);
dynPaths[moduleName] = compilePathUrl +
fileName.substr(baseUrl.length);
}
});
require({
// Karma serves files from '/base'
baseUrl: '/' + baseUrl,
paths: dynPaths,
shim: { /* ... */ },
deps: [ /* tests */ ],
// start test run, once requirejs is done
callback: function () {
window.__karma__.start();
}
});
Then as I edit the typescript files, they are compiled and put in the tmp folder as javascript files. These trigger karma's auto watch and it reruns the tests. In the tests, the require calls resolve correctly since we have explicitly overwritten the paths to the typescript files.
I realise that this is a bit hacky, but I had to jump through similar hoops while trying to include all my tests with REQUIRE_ADAPTER. So I assumed that there is no cleaner way of doing it.
Hopefully, if typescript becomes more prevalent, we will see better support for testing.
So here's ultimately what I've done: it turns out that Karma can handle/watch/serve files that are not within the base directory, and it makes them look to the browser in the form of "/absolute/C:/dir/folder/blah/file.js". This happens whenever files -> pattern starts with "../".
This feature can be used to make RequireJS see the whole directory structure exactly as it exists on the file system, thus allowing the tests to import app modules in the form of "../../Web/App/Module.ts".
files = [
// App files:
{ pattern: '../../Web/App/**/*', watched: true, served: true, included: false },
// Test files:
{ pattern: '../js/test/**/*.js', watched: true, served: true, included: false }
];
Reference (version 0.8): http://karma-runner.github.io/0.8/config/files.html
Since the typescript code is compiled to Javascript you can use all Javascript test frameworks.
I am using Jasmine: https://github.com/pivotal/jasmine/wiki
You can write your tests in Typescript with the .d.ts file here: https://github.com/borisyankov/DefinitelyTyped/blob/master/jasmine/jasmine.d.ts
But my client code is rather small and compiled to one output file, so I don't have the module issues that you describe.
Might be that I misunderstood your question - can't comment yet...
The runtime of the browser does not need any typescript information. So your test script should import the compiled ts files the same way as any other javascript files they need. Might be that you have to copy them to a subfolder of your test-project before you run your script.
I assume the bigger problem is that you have no interface information. Why do you want to import these informations instead of referencing them? Especially since importing them will also occur in the browser.
The Reference will only take place in the IDE , so it does not matter in which folders the interface-files are located.
/// <reference path="../../Web/Scripts/SubComponent/Module/References.ts" />