ionic 的手脚架结合了yeoman,用 grunt 管理工程。那么,终于开始看野猪了么。
对比用 yo 生成的工程和 ionic 生成的工程。发现没有 karma.conf.js 和 karma-e2e.conf.js。
作为一个牛逼的应用,怎么能没测试。据说在 Gruntfile.js 里面,我一打开这个文件,“嗡"一个头两个大格格大。
声明:
<!-- lang: js -->
// Generated on 2014-05-14 using generator-ionic 0.3.1
'use strict';
要用到的:
var _ = require('lodash');
var path = require('path');
var cordova = require('cordova');
var spawn = require('child_process').spawn;
入口,自动加载和刷新频率:
module.exports = function (grunt) {
// Load grunt tasks automatically
require('load-grunt-tasks')(grunt);
// Time how long tasks take. Can help when optimizing build times
require('time-grunt')(grunt);
配置入口:
// Define the configuration for all the tasks
grunt.initConfig({
工程配置:
路径
// Project settings
yeoman: {
// configurable paths
app: 'app',
scripts: 'scripts',
styles: 'styles',
images: 'images'
},
监控文件:
// Watches files for changes and runs tasks based on the changed files
watch: {
js: {
files: ['<%= yeoman.app %>/<%= yeoman.scripts %>/**/*.js'],
tasks: ['newer:jshint:all'],
options: {
livereload: true
}
},
styles: {
files: ['<%= yeoman.app %>/<%= yeoman.styles %>/**/*.css'],
tasks: ['newer:copy:styles', 'autoprefixer']
},
gruntfile: {
files: ['Gruntfile.js']
},
livereload: {
options: {
livereload: '<%= connect.options.livereload %>'
},
files: [
'<%= yeoman.app %>/*.html',
'<%= yeoman.app %>/templates/**/*.html',
'.tmp/<%= yeoman.styles %>/**/*.css',
'<%= yeoman.app %>/<%= yeoman.images %>/**/*.{png,jpg,jpeg,gif,webp,svg}'
]
}
},
Grunt服务器配置:
// The actual grunt server settings
connect: {
options: {
port: 9000,
// Change this to '0.0.0.0' to access the server from outside.
hostname: 'localhost',
livereload: 35729
},
livereload: {
options: {
open: true,
base: [
'.tmp',
'<%= yeoman.app %>'
]
}
},
dist: {
options: {
base: 'www'
}
},
coverage: {
options: {
port: 9002,
open: true,
base: ['coverage']
}
}
},
做文件拼写检查,check-style:
// Make sure code styles are up to par and there are no obvious mistakes
jshint: {
options: {
jshintrc: '.jshintrc',
reporter: require('jshint-stylish')
},
all: [
'Gruntfile.js',
'<%= yeoman.app %>/<%= yeoman.scripts %>/**/*.js'
],
test: {
options: {
jshintrc: 'test/.jshintrc'
},
src: ['test/unit/**/*.js']
}
},
刷新文件夹重新开始:
// Empties folders to start fresh
clean: {
dist: {
files: [{
dot: true,
src: [
'.tmp',
'www/*',
'!www/.git*'
]
}]
},
server: '.tmp'
},
不懂:
// Add vendor prefixed styles
autoprefixer: {
options: {
browsers: ['last 1 version']
},
dist: {
files: [{
expand: true,
cwd: '.tmp/<%= yeoman.styles %>/',
src: '{,*/}*.css',
dest: '.tmp/<%= yeoman.styles %>/'
}]
}
},
好鸟,是自动把 bower 的包给写入 index.html 里面么?有什么意义?
// Automatically inject Bower components into the app
'bower-install': {
app: {
html: '<%= yeoman.app %>/index.html',
ignorePath: '<%= yeoman.app %>/'
}
},
还是不懂:
// Reads HTML for usemin blocks to enable smart builds that automatically
// concat, minify and revision files. Creates configurations in memory so
// additional tasks can operate on them
useminPrepare: {
html: '<%= yeoman.app %>/index.html',
options: {
dest: 'www',
flow: {
html: {
steps: {
js: ['concat', 'uglifyjs'],
css: ['cssmin']
},
post: {}
}
}
}
},
// Performs rewrites based on the useminPrepare configuration
usemin: {
html: ['www/**/*.html'],
css: ['www/<%= yeoman.styles %>/**/*.css'],
options: {
assetsDirs: ['www']
}
},
还是不懂,打包压缩么:
// The following *-min tasks produce minified files in the dist folder
cssmin: {
options: {
root: '<%= yeoman.app %>',
noRebase: true
}
},
htmlmin: {
dist: {
options: {
collapseWhitespace: true,
collapseBooleanAttributes: true,
removeCommentsFromCDATA: true,
removeOptionalTags: true
},
files: [{
expand: true,
cwd: 'www',
src: ['*.html', 'templates/**/*.html'],
dest: 'www'
}]
}
},
还是不懂,打包结束之后拷贝到运行目录么:
// Copies remaining files to places other tasks can use
copy: {
dist: {
files: [{
expand: true,
dot: true,
cwd: '<%= yeoman.app %>',
dest: 'www',
src: [
'images/**/*.{png,jpg,jpeg,gif,webp,svg}',
'*.html',
'templates/**/*.html',
'fonts/*',
'res/*'
]
}, {
expand: true,
cwd: '.tmp/<%= yeoman.images %>',
dest: 'www/<%= yeoman.images %>',
src: ['generated/*']
}]
},
styles: {
expand: true,
cwd: '<%= yeoman.app %>/<%= yeoman.styles %>',
dest: '.tmp/<%= yeoman.styles %>/',
src: '{,*/}*.css'
},
fonts: {
expand: true,
cwd: 'app/bower_components/ionic/release/fonts/',
dest: '<%= yeoman.app %>/fonts/',
src: '*'
},
vendor: {
expand: true,
cwd: '<%= yeoman.app %>/vendor',
dest: '.tmp/<%= yeoman.styles %>/',
src: '{,*/}*.css'
},
all: {
expand: true,
cwd: '<%= yeoman.app %>/',
src: '**',
dest: 'www/'
}
},
并行处理任务,加速编译过程。原来拷贝就是编译:
// Run some tasks in parallel to speed up the build process
concurrent: {
server: [
'copy:styles',
'copy:vendor',
'copy:fonts'
],
test: [
'copy:styles',
'copy:vendor',
'copy:fonts'
],
dist: [
'copy:styles',
'copy:vendor',
'copy:fonts'
]
},
感觉是跟上面的 usemin 一伙的,让页面到底使用 min 格式文件或者是未压缩格式文件:
// By default, your `index.html`'s <!-- Usemin block --> will take care of
// minification. These next options are pre-configured if you do not wish
// to use the Usemin blocks.
// cssmin: {
// dist: {
// files: {
// 'www/<%= yeoman.styles %>/main.css': [
// '.tmp/<%= yeoman.styles %>/**/*.css',
// '<%= yeoman.app %>/<%= yeoman.styles %>/**/*.css'
// ]
// }
// }
// },
// uglify: {
// dist: {
// files: {
// 'www/<%= yeoman.scripts %>/scripts.js': [
// 'www/<%= yeoman.scripts %>/scripts.js'
// ]
// }
// }
// },
// concat: {
// dist: {}
// },
测试部分的配置,这部分权限最高,如果你在这里写的话就会覆盖掉 karma.conf.js 文件。
怪不得我看不到 yo 出来的那个 karma.conf.js :
// Test settings
// These will override any config options in karma.conf.js if you create it.
karma: {
options: {
basePath: '',
frameworks: ['mocha', 'chai'],
files: [
'<%= yeoman.app %>/bower_components/angular/angular.js',
'<%= yeoman.app %>/bower_components/angular-animate/angular-animate.js',
'<%= yeoman.app %>/bower_components/angular-sanitize/angular-sanitize.js',
'<%= yeoman.app %>/bower_components/angular-ui-router/release/angular-ui-router.js',
'<%= yeoman.app %>/bower_components/ionic/release/js/ionic.js',
'<%= yeoman.app %>/bower_components/ionic/release/js/ionic-angular.js',
'<%= yeoman.app %>/bower_components/angular-mocks/angular-mocks.js',
'<%= yeoman.app %>/<%= yeoman.scripts %>/**/*.js',
'test/mock/**/*.js',
'test/spec/**/*.js'
],
autoWatch: false,
reporters: ['dots', 'coverage'],
port: 8080,
singleRun: false,
preprocessors: {
// Update this if you change the yeoman config path
'app/scripts/**/*.js': ['coverage']
},
coverageReporter: {
reporters: [
{ type: 'html', dir: 'coverage/' },
{ type: 'text-summary' }
]
}
},
unit: {
// Change this to 'Chrome', 'Firefox', etc. Note that you will need
// to install a karma launcher plugin for browsers other than Chrome.
browsers: ['PhantomJS'],
background: true
},
continuous: {
browsers: ['PhantomJS'],
singleRun: true,
}
},
不懂啊……
// ngmin tries to make the code safe for minification automatically by
// using the Angular long form for dependency injection. It doesn't work on
// things like resolve or inject so those have to be done manually.
ngmin: {
dist: {
files: [{
expand: true,
cwd: '.tmp/concat/<%= yeoman.scripts %>',
src: '*.js',
dest: '.tmp/concat/<%= yeoman.scripts %>'
}]
}
}
});
注册 Cordova 配置,在 build 的模式下将会使用命名空间 cordova'build ?
不懂啊……
// Register tasks for all Cordova commands, but namespace
// the cordova:build since we already have a build task.
_.functions(cordova).forEach(function (name) {
name = (name === 'build') ? 'cordova:build' : name;
grunt.registerTask(name, function () {
this.args.unshift(name.replace('cordova:', ''));
// Handle URL's being split up by Grunt because of `:` characters
if (_.contains(this.args, 'http') || _.contains(this.args, 'https')) {
this.args = this.args.slice(0, -2).concat(_.last(this.args, 2).join(':'));
}
var done = this.async();
var cmd = path.resolve('./node_modules/cordova/bin', 'cordova.cmd');
var child = spawn(cmd, this.args);
child.stdout.on('data', function (data) {
grunt.log.writeln(data);
});
child.stderr.on('data', function (data) {
grunt.log.error(data);
});
child.on('close', function (code) {
code = (name === 'cordova:build') ? true : code ? false : true;
done(code);
});
});
});
你觉得我能看懂啊……关于模拟器的配置:
// Since Apache Ripple serves assets directly out of their respective platform
// directories, we watch all registered files and then copy all un-built assets
// over to www/. Last step is running Cordova prepare so we can refresh the ripple
// browser tab to see the changes.
grunt.registerTask('ripple', ['bower-install', 'copy:all', 'prepare', 'ripple-emulator']);
grunt.registerTask('ripple-emulator', function () {
grunt.config.set('watch', {
all: {
files: _.flatten(_.pluck(grunt.config.get('watch'), 'files')),
tasks: ['copy:all', 'prepare']
}
});
var cmd = path.resolve('./node_modules/ripple-emulator/bin', 'ripple');
var child = spawn(cmd, ['emulate']);
child.stdout.on('data', function (data) {
grunt.log.writeln(data);
});
child.stderr.on('data', function (data) {
grunt.log.error(data);
});
process.on('exit', function (code) {
child.kill('SIGINT');
process.exit(code);
});
return grunt.task.run(['watch']);
});
动态配置 Karma 服务,避免在启动 Grunt Serve 的时候连带把 Karma给启动起来:
// Dynamically configure `karma` target of `watch` task so that
// we don't have to run the karma test server as part of `grunt serve`
grunt.registerTask('watch:karma', function () {
var karma = {
files: ['<%= yeoman.app %>/<%= yeoman.scripts %>/**/*.js', 'test/spec/**/*.js'],
tasks: ['newer:jshint:test', 'karma:unit:run']
};
grunt.config.set('watch', karma);
return grunt.task.run(['watch']);
});
grunt.registerTask('serve', function (target) {
if (target === 'dist') {
return grunt.task.run(['build', 'connect:dist:keepalive']);
}
grunt.task.run([
'clean:server',
'bower-install',
'concurrent:server',
'autoprefixer',
'connect:livereload',
'watch'
]);
});
grunt.registerTask('test', [
'clean:server',
'concurrent:test',
'autoprefixer',
'karma:unit:start',
'watch:karma'
]);
grunt.registerTask('build', [
'clean:dist',
'bower-install',
'useminPrepare',
'concurrent:dist',
'autoprefixer',
'concat',
'ngmin',
'copy:dist',
'cssmin',
'uglify',
'usemin',
'htmlmin',
'cordova:build'
]);
grunt.registerTask('cordova', ['copy:all', 'cordova:build']);
grunt.registerTask('coverage', ['karma:continuous', 'connect:coverage:keepalive']);
grunt.registerTask('default', [
'newer:jshint',
'karma:continuous',
'build'
]);
};