I'm a Grunt newbie and I'm trying to convert an Angular app (based on angular-seed) to use it for CSS/JS concat/minification. I have an index.html file that defines the CSS and JS files:
<!-- build:css css/myjmh.css -->
<link rel="stylesheet" href="lib/bootstrap/bootstrap.min.css"/>
<link rel="stylesheet" href="lib/font-awesome/font-awesome.min.css"/>
<link rel="stylesheet" href="lib/toaster/toaster.css"/>
<link rel="stylesheet" href="css/app.css"/>
<link rel="stylesheet" href="css/custom.css"/>
<link rel="stylesheet" href="css/responsive.css"/>
<!-- endbuild -->
...
<!-- build:js js/myjmh.min.js -->
<script src="lib/jquery/jquery-1.10.2.min.js"></script>
<script src="lib/bootstrap/bootstrap.min.js"></script>
<script src="lib/angular/angular.min.js"></script>
<script src="lib/angular/angular-animate.min.js"></script>
<script src="lib/angular/angular-cookies.min.js"></script>
<script src="lib/angular/angular-resource.min.js"></script>
<script src="lib/angular/angular-route.min.js"></script>
<script src="lib/fastclick.min.js"></script>
<script src="lib/toaster/toaster.js"></script>
<script src="lib/webshim/modernizr.min.js"></script>
<script src="lib/webshim/polyfiller.min.js"></script>
<script src="js/app.js"></script>
<script src="js/services.js"></script>
<script src="js/controllers.js"></script>
<script src="js/filters.js"></script>
<script src="js/directives.js"></script>
<!-- endbuild -->
I'm trying to use grunt-usemin and its useminPrepare task to grab these values from my HTML.
Here's my Gruntfile.js:
module.exports = function (grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
clean: ["dist"],
copy: {
main: {
src: 'app/index.html',
dest: 'dist/index.html'
}
},
useminPrepare: {
html: 'app/index.html'
},
usemin: {
html: ['dist/index.html']
},
ngmin: {
dist: {
files: [
{
expand: true,
cwd: '.tmp/concat/js',
src: '*.js',
dest: '.tmp/concat/js'
}
]
}
}
});
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-cssmin');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-ngmin');
grunt.loadNpmTasks('grunt-usemin');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.registerTask('default', [
'copy', 'useminPrepare', 'concat', 'ngmin', 'uglify', 'cssmin', 'usemin'
]);
};
With all these settings, a "dist" directory is created with all the artifacts and everything seems to be generated/concatenated and minified correctly. However, when I load the new page up in my browser, I get the following error:
Uncaught Error: [$injector:unpr] http://errors.angularjs.org/1.2.3/$injector/unpr?p0=aProvider%20%3C-%20a
It seems like Grunt is doing something that doesn't play well with Angular. Any ideas on what this might be?
I was able to solve this by turning off mangling in uglify:
uglify: {
options: {
report: 'min',
mangle: false
}
}
As you might notice from my Gruntfile.js, ngmin is already used and this doesn't seem to help.
Found answer here: https://stackoverflow.com/a/17239358/65681
You need to do one of two things:
Make all of your angular code minification-friendly. See: http://docs.angularjs.org/guide/di#dependency-injection_dependency-annotation
Use a tool like grunt-ngannotate
If you are having same issues with declarations as I had, you could also use ng-annotate in order to achieve this. Here is a link to github: https://github.com/mzgol/grunt-ng-annotate
My working configuration is:
ngAnnotate: {
options: {
singleQuotes: true,
regexp: '^(ng\n?[\\ ]+(.*)|(module.*))$'
},
prod: {
files: [{
expand: true,
src: 'dist/**/*.js'
}]
}
}
Use it carefully as it will modify existing files. My assumption is that files in 'dist' folder should be generated by Grunt and can be deleted at any time.
As others have stated, it seems that you have a problem with minification. I see that you're using ngmin. But ngmin may not work when a file with mixed patterns is thrown at it. I mean, make sure that all of the files that you pass to ngmin don't already have minification-safe pattern.
Mangle false shouldn't be the solution, actually the code is not minification friendly, but the issues that I had were not explicitly described here, so they were:
1) "Make sure you only define each module with the angular.module(name, [requires]) syntax once across your entire project. Retrieve it for subsequent use with angular.module(name)" - I used it wrong in many places and it worked with mangle:false, breaking when setting mangle:true
2) I used bootstrap modal windows specifying instance controller not as a string, I've found the solution here
Related
I am new to angularjs and I am facing this error while i run my test using karma-jasmine
Uncaught Error: [$injector:nomod] Module 'app' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.
http://errors.angularjs.org/1.6.6/$injector/nomod?p0=app
at bower_components/angular/angular.js:2297
describe('LoginController', function () {
beforeEach(module('app'));
var $controller;
beforeEach(inject(function(_$controller_){
$controller = _$controller_;
}));
describe('homeBtn', function () {
it('whether its sign in or register', function () {
var $scope = {};
var controller = $controller('LoginController', { $scope: $scope });
$scope.homeBtn("Register");
expect($scope.signinerror).toBeFalsy();
expect($scope.tryagain).toBeFalsy();
expect($scope.registererror).toBeFalsy();
expect($scope.modal_title).toBe("Register");
});
});
});
this is my app.js
var app = angular.module('app', ['ngRoute','ngWebSocket','ng.epoch']);
This is my Login Controller
angular.module('app').controller('LoginController',['$rootScope','$scope','$location','$http', function($rootScope,$scope,$location,$http){
$scope.homeBtn = function(keyvalue){
$scope.signinerror=false;
$scope.tryagain=false;
$scope.registererror=false;
if(keyvalue == "Register"){
$scope.modal_title="Register";
$scope.reg=true;
}
else{
$scope.reg=false;
$scope.modal_title="Sign In";
}
}
Here is my HTML code
<!DOCTYPE html>
<html ng-app="app">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title></title>
<link rel="stylesheet"href="bower_components/bootstrap/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="bower_components/epoch/dist/css/epoch.min.css">
<link rel="stylesheet" href="app/css/index.css">
</head>
<body>
<ng-view> </ng-view>
<!--Bower Components-->
<script src="bower_components/jquery/dist/jquery.min.js"></script>
<script src="bower_components/bootstrap/dist/js/bootstrap.min.js"></script>
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-route/angular-route.min.js"></script>
<script src="bower_components/angular-websocket/dist/angular-websocket.min.js"></script>
<script src="bower_components/d3/d3.js"></script>
<script src="bower_components/epoch/dist/js/epoch.min.js"></script>
<script src="bower_components/ng-epoch/ng-epoch.js"></script>
<script src="bower_components/js-md5/src/md5.js"></script>
<!--MainJS-->
<script src="app/js/app.js"></script>
<!--Controllers-->
<script src="app/js/Controllers/LoginController.js"></script>
<script src="app/js/Controllers/HomeController.js"></script>
<!--custom js files-->
<script src="app/js/libraries/common.js"></script>
<script src="app/js/libraries/smoothie.js"></script>
<!--directives-->
<!--factories-->
<script src="app/js/Factories/websocketservice.js"></script>
</body>
</html>
and here is my karma config file
module.exports = function(config) {
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '',
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['jasmine'],
// list of files / patterns to load in the browser
files: [
'bower_components/angular/angular.js',
'bower_components/angular-mocks/angular-mocks.js',
'bower_components/angular-resource/angular-resource.js',
'app/**/*.js',
'test/**/*.js'
],
// list of files to exclude
exclude: [
],
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
},
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['progress'],
// web server port
port: 9876,
// enable / disable colors in the output (reporters and logs)
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: true,
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: ['Chrome'],
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
singleRun: false,
// Concurrency level
// how many browser should be started simultaneous
concurrency: Infinity
})
}
I think it can be more generic question. How can I add custom (vendor) scripts to yeoman yo generated app. I installed angular charts with npm (npm install angular-chart --save). I add scripts to index.html.
<!-- build:js(.) scripts/vendor.js -->
<!-- bower:js -->
<script src="bower_components/jquery/dist/jquery.js"></script>
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/bootstrap/dist/js/bootstrap.js"></script>
<script src="bower_components/angular-animate/angular-animate.js"></script>
<script src="bower_components/angular-cookies/angular-cookies.js"></script>
<script src="bower_components/angular-resource/angular-resource.js"></script>
<script src="bower_components/angular-route/angular-route.js"></script>
<script src="bower_components/angular-sanitize/angular-sanitize.js"></script>
<script src="bower_components/angular-touch/angular-touch.js"></script>
<!-- endbower -->
<script src="node_modules/chart.js/Chart.min.js"></script>
<script src="node_modules/angular-chart.js/dist/angular-chart.min.js"></script>
<!-- endbuild -->
But making distribution with grunt does not create proper minified script.
scripts.226f19b7.js:1 Uncaught Error: Chart.js library needs to be included, see http://jtblin.github.io/angular-chart.js/
Include the library in your grunt task build (Gruntfile.js)
You will find this task or something like it:
grunt.registerTask('build', [
'clean:dist',
'wiredep',
'useminPrepare',
'concurrent:dist',
'postcss',
'concat',
'copy:dist',
'cdnify',
'cssmin',
'uglify',
'filerev',
'usemin',
'htmlmin'
]);
I think you would find your solution in copy:dist task
copy: {
dist: {
files: [{
expand: true,
dot: true,
cwd: '<%= yeoman.app %>',
dest: '<%= yeoman.dist %>',
src: [
'*.{ico,png,txt}',
'*.html',
'images/{,*/}*.{webp}',
'styles/fonts/{,*/}*.*'
]
}, {
expand: true,
cwd: 'bower_components/bootstrap/dist',
src: 'fonts/*',
dest: '<%= yeoman.dist %>'
},
{
expand:true,
cwd:'node_modules/yourlib/,
src:'*',
dest:'<%=yeoman.dist %>'
}
Thanks to webenegized I finally found the problem and the solution.
At first, it should work out of the box!! No gruntfile.js modification are needed or any other action. Just npm install dependecies, put the required script tag(s) in the index.html and voila. The magic of yeoman generator.
The error I encouter was caused by the copy/paste of incorrect script tags values from the example on the angular-chart.js website (see: installation section). The site example states:
<script src="node_modules/chart.js/Chart.min.js"></script>
but the real path should be
<script src="node_modules/chart.js/dist/Chart.min.js"></script> (dist folder is missing).
I should have spotted this because the error I've got clearly show that angular-chart.js was included and the complaint was about its dependency (chart.js). It turns out I'm less perceptive that I think.
I'm setting up a new AngularJS project (first time for me) and I'm finding it very touchy... Latest issue is getting bower to properly configure things in my index.html file.
If I just hardcode things to the googleapis, it all works just fine (example in the index.html file).
If I setup bower and do a grunt bowerInstall, it adds what look to be the correct lines in my index.html but they don't work at all. I get errors like:
Resource interpreted as Script but transferred with MIME type text/html: "http://localhost:8081/bower_components/angular/angular.js".
and
Uncaught SyntaxError: Unexpected token < for the angular files.
So far bower has been a royal pain... any ideas what's going wrong here? Thanks!
BTW, the simple app is working as expected and I've gotten some basic karma tests working.
bower.json:
{
"name": "meanjs_book",
"version": "0.1.0",
"homepage": "https://github.com/JESii/xxx",
"authors": [
"Jon Seidel <jseidel#edpci.com>"
],
"description": "Rudimentary app from MeanJS Book",
"main": "server.js",
"moduleType": [
"node"
],
"keywords": [
"MongoDB",
"Express",
"AngularJS",
"Node",
"HighPlans"
],
"license": "MIT",
"private": true,
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests",
"spec"
],
"dependencies": {
"angular-route": "~1.3.15",
"angular": "~1.3.15",
"angular-animate": "~1.3.15",
"angular-mocks": "~1.3.15"
}
}
Gruntfile
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
uglify: {
options: {
banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
},
build: {
src: 'src/<%= pkg.name %>.js',
dest: 'build/<%= pkg.name %>.min.js'
}
},
bowerInstall: {
target: {
// Point to the files that should be updated when
// you run `grunt bowerInstall`
src: ['public/app/views/index.html'], // index.html support
// Optional:
// ---------
cwd: '',
dependencies: true,
devDependencies: false,
exclude: [],
fileTypes: {},
ignorePath: '',
overrides: {}
}
}
});
// Load the plugin that provides the "uglify" task.
grunt.loadNpmTasks('grunt-contrib-uglify');
// Default task(s).
grunt.registerTask('default', ['uglify']);
grunt.loadNpmTasks('grunt-bower-install');
};
index.html:
<!-- AngularJS -->
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js" type="text/javascript" charset="utf-8"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-route.js" type="text/javascript" charset="utf-8"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-animate.js" type="text/javascript" charset="utf-8"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-mocks.js" type="text/javascript" charset="utf-8"></script>
<!-- bower:js -->
<!-- <script src="../../../bower_components/angular/angular.js" type="text/javascript"></script> -->
<!-- <script src="../../../bower_components/angular-route/angular-route.js" type="text/javascript"></script> -->
<!-- <script src="../../../bower_components/angular-animate/angular-animate.js" type="text/javascript"></script> -->
</script> -->
I'm assuming you're getting that error when running grunt serve. The console message you pasted indicates that grunt isn't allowing JS files through. If a particular file type isn't allowed, it's replaced with index.html which is why it was transferred with MIME type HTML. Where did you get that Gruntfile from?
Additionally, I recommend using Yeoman to scaffold a basic Angular app. Check it out here: http://yeoman.io/codelab.html. It's very easy to follow and will make you more comfortable with using tools like Grunt & Bower.
My directory structure looks like this, I have an absolute path /static mapped to my public dir
+public/
+--app/
+--dist/
|--Gruntfile.js
|..
|..
Currently, everything builds fine and I wind up with my minified/rev'd files in dist but the index.html file contains relative paths like:
<script src="some-file.56a75bad.js"></script>
When I need them to be:
<script src="/static/dist/some-file.56a75bad.js"></script>
Can't seem to figure out how to achieve this, everything I've tried winds up giving my the right file paths but not the rev'd file, i.e:
<script src="/static/dist/some-file.js"></script>
My solution: copy task moves all scripts to a .tmp folder. uglify task then runs and outputs to a folder hierarchy in .tmp that the absolute paths to resolve to. After those 2 tasks are run I have a folder structure that looks like this (mid build):
public/
+--.tmp/
+--static/
+--dist/
|--application.min.js
|--dependencies.min.js
+--app/
+--bower_components/
+--dist/
|--Gruntfile.js
|--index.html
A little piece of my index.html
<!-- build:js /static/dist/dependencies.min.js -->
<script src="/static/dist/dependencies.min.js"></script>
<!-- endbuild -->
<!-- build:js /static/dist/application.min.js -->
<script src="/static/dist/application.min.js"></script>
<!-- endbuild -->
Now it's business as usual, the useminPrepare filerev and usemin tasks are run.
And my GruntFile:
module.exports = function(grunt) {
require('load-grunt-tasks')(grunt);
grunt.config.init({
useminPrepare: {
html: 'dist/index.html',
options: {
dest: './dist'
}
},
usemin:{
html:['dist/index.html'],
options: {
assetsDirs: ['.tmp']
}
},
copy:{
html: {
src: './index.html',
dest: 'dist/index.html'
},
javascripts: {
cwd: '',
src: 'app/scripts/**',
dest: '.tmp',
expand: true
}
},
uglify: {
build: {
options: {
mangle: true
},
files: {
'.tmp/static/dist/application.min.js': ['.tmp/app/**/*.js'],
'.tmp/static/dist/dependencies.min.js': [
'bower_components/jquery/dist/jquery.js',
'bower_components/angular/angular.js'
// All my other 3rd party libs, I realized this can be done in index.html but there's environmental constraints making that not possible
]
}
}
},
filerev: {
dist: {
src: ['.tmp/static/dist/application.min.js', '.tmp/static/dist/dependencies.min.js'],
dest: 'dist'
}
}
});
grunt.registerTask('build',[
'copy:javascripts',
'copy:html',
'uglify',
'useminPrepare',
'filerev',
'usemin'
]);
};
yo angular sets up a basic scaffold for writing angular apps. So in index.html we have code as follows:
<!-- build:css(.) styles/vendor.css -->
<!-- bower:css -->
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap-theme.css" />
<!-- endbower -->
<!-- endbuild -->
I am looking for a task that would remove those lines and inject the correct location of the files. For e.g.:
<link rel="stylesheet" href="css/bootstrap.css" />
<link rel="stylesheet" href="css/bootstrap-theme.css" />
Or is there some alternate way to deal with this...like for e.g. have the watch task create different versions of index.html, say index.html.fooTarget; it gets updated with the correct paths for that target; and then when it comes to the grunt task have the latter file copied appropriately.
What can be done to deal with this?
Maybe you should let the paths be as they are since CSS files are bower dependencies, too. Copy the dependencies to a directory with the same relative path?
But if you will, I think this will help you modify your grunt tasks.
I assume you have (at least) the following in your package.json.
{
"name": "app",
"version": "0.0.1",
"dependencies": {},
"devDependencies": {
"grunt": "~0.4.1",
"grunt-contrib-copy": "~0.4.1",
"grunt-text-replace": "0.3.11"
}
}
Then the minimal copy & replace tasks could look like the following in your Gruntfile.js (you can combine copy and replace by using grunt-contrib-copy alone, if you will).
'use strict';
module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-text-replace');
grunt.initConfig({
src: 'src',
dest: 'dest',
copy: {
dev: {
expand: true,
cwd: '<%= src %>',
src: '{,*/}*.html',
dest: '<%= dest %>/'
}
},
replace: {
dev: {
src: ['<%= dest %>/{,*/}*.html'],
overwrite: true,
replacements: [{
from: 'bower_components/bootstrap/dist/css',
to: 'css'
}]
}
}
});
grunt.registerTask('prepare', function(val) {
var target = val || 'dev';
grunt.task.run([
'copy:' + target,
'replace:' + target
]);
});
};
Now when you run prepare task with desired target it will copy all HTML files from src to dest directory and have desired CSS paths replaced, leaving original files intact.
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css" />
--> will be replaced as -->
<link rel="stylesheet" href="css/bootstrap.css" />