使用Grunt(Yeoman)的多个构build文件夹(多个客户端,多个任务,多个目标)

我正在构build一个项目,它将是一个webapp(浏览器可运行)和一个Phonegap应用程序(iOS和Android)。 尽pipe我的项目在理论上可以使用我的Yeoman生成的相同的dist文件夹,但是Grunt任务通过运行grunt build build生产就绪代码。 我想运行一些像grunt build_webgrunt build_iosgrunt build_android ,为每个平台单独构build生产代码。 或grunt build:webgrunt build:iosgrunt build:android 。 这样,我可以自定义一些加载的脚本,图像等,每个都有自己的构build指令。

那么,我是否应该通过我的Gruntfile复制粘贴所有的dist并负责任地build指令? 或者,对此有最佳做法吗? (试过这个,没有工作)

Yeoman伙计,这可能吗?

这是我目前的Gruntfile.js ,以防万一。

 'use strict'; var LIVERELOAD_PORT = 35729; var lrSnippet = require('connect-livereload')({port: LIVERELOAD_PORT}); var mountFolder = function (connect, dir) { return connect.static(require('path').resolve(dir)); }; // # Globbing // for performance reasons we're only matching one level down: // 'test/spec/{,*/}*.js' // use this if you want to recursively match all subfolders: // 'test/spec/**/*.js' module.exports = function (grunt) { // show elapsed time at the end require('time-grunt')(grunt); // load all grunt tasks require('load-grunt-tasks')(grunt); // configurable paths var yeomanConfig = { app: 'app', dist: '../www' }; grunt.initConfig({ yeoman: yeomanConfig, watch: { coffee: { files: ['<%= yeoman.app %>/scripts/{,*/}*.coffee'], tasks: ['coffee:dist'] }, coffeeTest: { files: ['test/spec/{,*/}*.coffee'], tasks: ['coffee:test'] }, compass: { files: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'], tasks: ['compass:server', 'autoprefixer'] }, styles: { files: ['<%= yeoman.app %>/styles/{,*/}*.css'], tasks: ['copy:styles', 'autoprefixer'] }, livereload: { options: { livereload: LIVERELOAD_PORT }, files: [ '<%= yeoman.app %>/*.html', '.tmp/styles/{,*/}*.css', '{.tmp,<%= yeoman.app %>}/scripts/{,*/}*.js', '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}' ] } }, connect: { options: { port: 9000, // change this to '0.0.0.0' to access the server from outside hostname: 'localhost' }, livereload: { options: { middleware: function (connect) { return [ lrSnippet, mountFolder(connect, '.tmp'), mountFolder(connect, yeomanConfig.app) ]; } } }, test: { options: { middleware: function (connect) { return [ mountFolder(connect, '.tmp'), mountFolder(connect, 'test'), mountFolder(connect, yeomanConfig.app) ]; } } }, dist: { options: { middleware: function (connect) { return [ mountFolder(connect, yeomanConfig.dist) ]; } } } }, open: { server: { path: 'http://localhost:<%= connect.options.port %>' } }, clean: { options: { force: true }, dist: { files: [{ dot: true, src: [ '.tmp', '<%= yeoman.dist %>/*', '!<%= yeoman.dist %>/.git*' ] }] }, server: '.tmp' }, jshint: { options: { jshintrc: '.jshintrc' }, all: [ 'Gruntfile.js', '<%= yeoman.app %>/scripts/{,*/}*.js', '!<%= yeoman.app %>/scripts/vendor/*', 'test/spec/{,*/}*.js' ] }, mocha: { all: { options: { run: true, urls: ['http://localhost:<%= connect.options.port %>/index.html'] } } }, coffee: { dist: { files: [{ expand: true, cwd: '<%= yeoman.app %>/scripts', src: '{,*/}*.coffee', dest: '.tmp/scripts', ext: '.js' }] }, test: { files: [{ expand: true, cwd: 'test/spec', src: '{,*/}*.coffee', dest: '.tmp/spec', ext: '.js' }] } }, compass: { options: { sassDir: '<%= yeoman.app %>/styles', cssDir: '.tmp/styles', generatedImagesDir: '.tmp/images/generated', imagesDir: '<%= yeoman.app %>/images', javascriptsDir: '<%= yeoman.app %>/scripts', fontsDir: '<%= yeoman.app %>/styles/fonts', importPath: '<%= yeoman.app %>/bower_components', httpImagesPath: '/images', httpGeneratedImagesPath: '/images/generated', httpFontsPath: '/styles/fonts', relativeAssets: false }, dist: { options: { generatedImagesDir: '<%= yeoman.dist %>/images/generated' } }, server: { options: { debugInfo: true } } }, autoprefixer: { options: { browsers: ['last 1 version'] }, dist: { files: [{ expand: true, cwd: '.tmp/styles/', src: '{,*/}*.css', dest: '.tmp/styles/' }] } }, // not used since Uglify task does concat, // but still available if needed /*concat: { dist: {} },*/ requirejs: { dist: { // Options: https://github.com/jrburke/r.js/blob/master/build/example.build.js options: { // `name` and `out` is set by grunt-usemin baseUrl: yeomanConfig.app + '/scripts', optimize: 'none', // TODO: Figure out how to make sourcemaps work with grunt-usemin // https://github.com/yeoman/grunt-usemin/issues/30 //generateSourceMaps: true, // required to support SourceMaps // http://requirejs.org/docs/errors.html#sourcemapcomments preserveLicenseComments: false, useStrict: true, wrap: true //uglify2: {} // https://github.com/mishoo/UglifyJS2 } } }, rev: { dist: { files: { src: [ '<%= yeoman.dist %>/scripts/{,*/}*.js', '<%= yeoman.dist %>/styles/{,*/}*.css', '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp}', '<%= yeoman.dist %>/styles/fonts/{,*/}*.*' ] } } }, useminPrepare: { options: { dest: '<%= yeoman.dist %>' }, html: '<%= yeoman.app %>/index.html' }, usemin: { options: { dirs: ['<%= yeoman.dist %>'] }, html: ['<%= yeoman.dist %>/{,*/}*.html'], css: ['<%= yeoman.dist %>/styles/{,*/}*.css'] }, imagemin: { dist: { files: [{ expand: true, cwd: '<%= yeoman.app %>/images', src: '{,*/}*.{png,jpg,jpeg}', dest: '<%= yeoman.dist %>/images' }] } }, svgmin: { dist: { files: [{ expand: true, cwd: '<%= yeoman.app %>/images', src: '{,*/}*.svg', dest: '<%= yeoman.dist %>/images' }] } }, cssmin: { // This task is pre-configured if you do not wish to use Usemin // blocks for your CSS. By default, the Usemin block from your // `index.html` will take care of minification, eg // // <!-- build:css({.tmp,app}) styles/main.css --> // // dist: { // files: { // '<%= yeoman.dist %>/styles/main.css': [ // '.tmp/styles/{,*/}*.css', // '<%= yeoman.app %>/styles/{,*/}*.css' // ] // } // } }, htmlmin: { dist: { options: { /*removeCommentsFromCDATA: true, // https://github.com/yeoman/grunt-usemin/issues/44 //collapseWhitespace: true, collapseBooleanAttributes: true, removeAttributeQuotes: true, removeRedundantAttributes: true, useShortDoctype: true, removeEmptyAttributes: true, removeOptionalTags: true*/ }, files: [{ expand: true, cwd: '<%= yeoman.app %>', src: '*.html', dest: '<%= yeoman.dist %>' }] } }, // Put files not handled in other tasks here copy: { dist: { files: [{ expand: true, dot: true, cwd: '<%= yeoman.app %>', dest: '<%= yeoman.dist %>', src: [ '*.{ico,png,txt}', '.htaccess', 'images/{,*/}*.{webp,gif}', 'styles/fonts/{,*/}*.*' ] }] }, styles: { expand: true, dot: true, cwd: '<%= yeoman.app %>/styles', dest: '.tmp/styles/', src: '{,*/}*.css' } }, modernizr: { devFile: '<%= yeoman.app %>/bower_components/modernizr/modernizr.js', outputFile: '<%= yeoman.dist %>/bower_components/modernizr/modernizr.js', files: [ '<%= yeoman.dist %>/scripts/{,*/}*.js', '<%= yeoman.dist %>/styles/{,*/}*.css', '!<%= yeoman.dist %>/scripts/vendor/*' ], uglify: true }, concurrent: { server: [ 'compass', 'coffee:dist', 'copy:styles' ], test: [ 'coffee', 'copy:styles' ], dist: [ 'coffee', 'compass', 'copy:styles', 'imagemin', 'svgmin', 'htmlmin' ] }, bower: { options: { exclude: ['modernizr'] }, all: { rjsConfig: '<%= yeoman.app %>/scripts/main.js' } } }); grunt.registerTask('server', function (target) { if (target === 'dist') { return grunt.task.run(['build', 'open', 'connect:dist:keepalive']); } grunt.task.run([ 'clean:server', 'concurrent:server', 'autoprefixer', 'connect:livereload', 'open', 'watch' ]); }); grunt.registerTask('test', [ 'clean:server', 'concurrent:test', 'autoprefixer', 'connect:test', 'mocha' ]); grunt.registerTask('build', [ 'clean:dist', 'useminPrepare', 'concurrent:dist', 'autoprefixer', 'requirejs', 'concat', 'cssmin', 'uglify', 'modernizr', 'copy:dist', 'rev', 'usemin' ]); grunt.registerTask('default', [ 'jshint', 'test', 'build' ]); }; 

除了PG 3的build议之外,真的没有最佳做法。对于多个客户端,您可能需要修改gruntjs。 这是我的项目结构,资产文件夹包含不同客户端的数据:

在这里输入图像描述

这里是我的gruntjs文件。 它使用ImageMagik创build所有必要的启动器图标。 build立和部署空中安装。 你明白了

 /*** * Package script for MobileStore ***/ 'use strict'; // require package.json: /* { "name": "MobileStore", "version": "1.0.1", "devDependencies": { "grunt": "~0.4.1", "grunt-contrib-jshint": "~0.6.0", "grunt-contrib-clean": "~0.4.1", "grunt-contrib-copy": "~0.4.1", "grunt-contrib-concat": "~0.3.0", "grunt-exec": "~0.4.2", "grunt-string-replace": "~0.2.4", "grunt-image-resize": "~0.2.0" } } */ module.exports = function(grunt) { var isMacOS = grunt.file.isDir('/etc'); // load all grunt tasks require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks); // configurable paths var myConfig = { dest: 'out/', platforms: ['ios', 'android'], chains: [11,52,75,119,129,224,229,235], chainConfig: {}, baseDir: __dirname + '/' }; var chainid = grunt.option('chainid'); if (chainid) { myConfig.chains = [chainid]; } // set up grunt var gruntConfig = { clean: { build: ['out', 'beta-assets'] }, copy: {}, exec: {}, "string-replace": {}, image_resize: {} }; // first thing to do is clean var packageConfig = ['clean']; // duplicate source to the chain // copy from platforms/(android/ios) to out/chainid/(android/ios) myConfig.chains.forEach(function(chain) { myConfig.platforms.forEach(function(platform) { var key = 'setup_' + platform + chain; // any platform is fine, we're just preping chainConfig if (platform == 'android') { myConfig.chainConfig[chain] = require('./YourCompany.Mobile/assets/' + chain + '/build.json'); } gruntConfig.copy[key] = { files: [ { expand: true, cwd: 'YourCompany.Mobile/platforms/' + platform + '/', src: ['./**'], dest: 'out/' + chain + '/' + platform + '/' } ] }; packageConfig.push('copy:' + key); }); }); // override the device resource (icon, splash, etc..) // copy from assets/chainid/device/(android/ios) to out/chainid/(android/ios) myConfig.chains.forEach(function(chain) { myConfig.platforms.forEach(function(platform) { var key = 'device_resource_' + platform + chain; var srcArt = 'YourCompany.Mobile/assets/' + chain + '/device/AndroidArtwork.png'; var dest = 'out/' + chain + '/' + platform + '/MobileStore/'; if (platform == 'android') { dest += 'res/'; } else if (platform == 'ios') { dest += 'MobileStore/'; } gruntConfig.copy[key] = { files: [ { expand: true, cwd: 'YourCompany.Mobile/assets/' + chain + '/device/' + platform + '/', src: ['./**'], dest: dest } ] }; packageConfig.push('copy:' + key); // do image manipulations // Use AnroidArtwork.png if (platform == 'android') { gruntConfig.image_resize[key + '_36'] = { options: { width: 36, height: 36, overwrite: true }, files: [{ src: srcArt, dest: dest + 'drawable-ldpi/icon.png' }] }; packageConfig.push('image_resize:' + key + '_36'); gruntConfig.image_resize[key + '_48'] = { options: { width: 48, height: 48, overwrite: true }, files: [{ src: srcArt, dest: dest + 'drawable-mdpi/icon.png' }] }; packageConfig.push('image_resize:' + key + '_48'); gruntConfig.image_resize[key + '_72'] = { options: { width: 72, height: 72, overwrite: true }, files: [{ src: srcArt, dest: dest + 'drawable-hdpi/icon.png' }] }; packageConfig.push('image_resize:' + key + '_72'); gruntConfig.image_resize[key + '_96'] = { options: { width: 96, height: 96, overwrite: true }, files: [{ src: srcArt, dest: dest + 'drawable-xhdpi/icon.png' }] }; packageConfig.push('image_resize:' + key + '_96'); gruntConfig.image_resize[key + '_default'] = { options: { width: 96, height: 96, overwrite: true }, files: [{ src: srcArt, dest: dest + 'drawable/icon.png' }] }; packageConfig.push('image_resize:' + key + '_default'); } else if (platform == 'ios') { gruntConfig.image_resize[key + '_57'] = { options: { width: 57, height: 57, overwrite: true }, files: [{ src: srcArt, dest: dest + 'Resources/icons/Icon.png' }] }; packageConfig.push('image_resize:' + key + '_57'); gruntConfig.image_resize[key + '_114'] = { options: { width: 114, height: 114, overwrite: true }, files: [{ src: srcArt, dest: dest + 'Resources/icons/Icon@2x.png' }] }; packageConfig.push('image_resize:' + key + '_114'); gruntConfig.image_resize[key + '_72'] = { options: { width: 72, height: 72, overwrite: true }, files: [{ src: srcArt, dest: dest + 'Resources/icons/Icon-72.png' }] }; packageConfig.push('image_resize:' + key + '_72'); gruntConfig.image_resize[key + '_144'] = { options: { width: 144, height: 144, overwrite: true }, files: [{ src: srcArt, dest: dest + 'Resources/icons/Icon-72@2x.png' }] }; packageConfig.push('image_resize:' + key + '_144'); gruntConfig.image_resize[key + '_29'] = { options: { width: 29, height: 29, overwrite: true }, files: [{ src: srcArt, dest: dest + 'Resources/icons/Icon-Small.png' }] }; packageConfig.push('image_resize:' + key + '_29'); gruntConfig.image_resize[key + '_58'] = { options: { width: 58, height: 58, overwrite: true }, files: [{ src: srcArt, dest: dest + 'Resources/icons/Icon-Small@2x.png' }] }; packageConfig.push('image_resize:' + key + '_58'); gruntConfig.image_resize[key + '_50'] = { options: { width: 50, height: 50, overwrite: true }, files: [{ src: srcArt, dest: dest + 'Resources/icons/Icon-Small-50.png' }] }; packageConfig.push('image_resize:' + key + '_50'); gruntConfig.image_resize[key + '_100'] = { options: { width: 100, height: 100, overwrite: true }, files: [{ src: srcArt, dest: dest + 'Resources/icons/Icon-Small-50@2x.png' }] }; packageConfig.push('image_resize:' + key + '_100'); gruntConfig.image_resize[key + '_1024'] = { options: { width: 1024, height: 1024, overwrite: true, upscale: true }, files: [{ src: srcArt, dest: dest + 'Resources/iTunesArtwork' }] }; packageConfig.push('image_resize:' + key + '_1024'); // setup images for ota deploy gruntConfig.image_resize[key + '_512x'] = { options: { width: 512, height: 512, overwrite: true, upscale: true }, files: [{ src: srcArt, dest: dest + '../iTunesArtwork.png' }] }; packageConfig.push('image_resize:' + key + '_512x'); gruntConfig.image_resize[key + '_57x'] = { options: { width: 57, height: 57, overwrite: true }, files: [{ src: srcArt, dest: dest + '../Icon.png' }] }; packageConfig.push('image_resize:' + key + '_57x'); } }); }); // copy assets to www folder // copy from www to out/chainid/... myConfig.chains.forEach(function(chain) { myConfig.platforms.forEach(function(platform) { var key = 'content_' + platform + chain; var dest = 'out/' + chain + '/' + platform + '/MobileStore/'; if (platform == 'android') { dest += 'assets/www/'; } else if (platform == 'ios') { dest += 'www/'; } gruntConfig.copy[key] = { files: [ { expand: true, cwd: 'YourCompany.Mobile/www/', src: ['./**'], dest: dest } ] }; packageConfig.push('copy:' + key); }); }); // copy assets override // copy from assets/chainid/www to out/chainid/... myConfig.chains.forEach(function(chain) { myConfig.platforms.forEach(function(platform) { var key = 'content_override_' + platform + chain; var dest = 'out/' + chain + '/' + platform + '/MobileStore/'; if (platform == 'android') { dest += 'assets/www/'; } else if (platform == 'ios') { dest += 'www/'; } gruntConfig.copy[key] = { files: [ { expand: true, cwd: 'YourCompany.Mobile/assets/' + chain + '/www/', src: ['./**'], dest: dest } ] }; packageConfig.push('copy:' + key); }); }); // copy phonegap merges // copy from merges to out/chainid/... myConfig.chains.forEach(function(chain) { myConfig.platforms.forEach(function(platform) { var key = 'phonegap_override_' + platform + chain; var dest = 'out/' + chain + '/' + platform + '/MobileStore/'; if (platform == 'android') { dest += 'assets/www/'; } else if (platform == 'ios') { dest += 'www/'; } gruntConfig.copy[key] = { files: [ { expand: true, cwd: 'YourCompany.Mobile/merges/' + platform, src: ['./**'], dest: dest } ] }; packageConfig.push('copy:' + key); }); }); // doing beta assets deployment myConfig.chains.forEach(function (chain) { var key = 'content_beta_assets_' + chain; var src = 'out/' + chain + '/ios/MobileStore/www/'; gruntConfig.copy[key] = { files: [ { expand: true, cwd: src, src: ['./**'], dest: 'beta-assets/' + chain + '/www/' } ] }; packageConfig.push('copy:' + key); // override cordova file gruntConfig.copy[key + '_pg'] = { files: [ { expand: true, cwd: 'YourCompany.Mobile/www/', src: ['./cordova.js'], dest: 'beta-assets/' + chain + '/www/' } ] }; packageConfig.push('copy:' + key + '_pg'); }); // doing android(ant) build myConfig.chains.forEach(function (chain) { var key = 'android_build_' + chain; var dest = 'out/' + chain + '/android/MobileStore/'; var destFolder = './out/' + chain + '/'; // use previous chainConfig to perform text replace gruntConfig["string-replace"][key + '_prep'] = { files: [ { expand: true, cwd: 'out/' + chain + '/', src: ['./**/**.java', './**/**.xml', './**/**.plist', './**/**.m'], dest: 'out/' + chain + '/' } ], options: { replacements: [ { pattern: /(net.yourcompany.MobileStore)+/ig, replacement: myConfig.chainConfig[chain].id }, { pattern: /(AppleBundleSeedID)+/ig, replacement: myConfig.chainConfig[chain].AppleBundleSeedID }, { pattern: '<string name="app_name">MobileStore</string>', replacement: '<string name="app_name">' + myConfig.chainConfig[chain].ApplicationName + '</string>' } ] } }; packageConfig.push('string-replace:' + key + '_prep'); // do build gruntConfig.exec[key] = { cmd: 'ant clean && ant debug', cwd: dest, env: process.env }; packageConfig.push('exec:' + key); }); // doing xcode build myConfig.chains.forEach(function(chain) { var key = 'ios_build_' + chain; var chainConfig = myConfig.chainConfig[chain]; var dest = myConfig.baseDir + 'out/' + chain + '/ios/MobileStore/'; var buildDir = dest + 'Build/Products/Release-iphoneos/'; var commandStart = 'export PATH="/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin:/Applications/Xcode.app/Contents/Developer/usr/bin:/usr/bin:/bin:/usr/sbin:/opt/local/bin:/opt/local/sbin:/sbin:/usr/local/bin:/Users/Shared/ImageMagick-6.8.6:$PATH" &&' var command = 'chmod 777 dobuild && ./dobuild "' + chainConfig.AppleProductName + '" ' + chain + ' ' + process.env.BUILD_NUMBER + ' "' + chainConfig.AppleBundleSeedID + '.' + chainConfig.id + '"'; grunt.log.debug('ios build:' + command); if (isMacOS) { gruntConfig.exec[key] = { cmd: command, cwd: dest, exitCode: 0 }; packageConfig.push('exec:' + key); } // prep apk for deployment gruntConfig.copy['android_build_' + chain + '_apk'] = { options: { processContent: false }, files: [ { expand: true, flatten: true, src: ['out/' + chain + '/android/MobileStore/bin/MobileStore-debug.apk'], dest: 'beta-assets/' + chain + '/', filter: 'isFile' } ] }; packageConfig.push('copy:' + 'android_build_' + chain + '_apk'); // prep ipa for ios deploy gruntConfig.copy['ios_build_' + chain + '_ipa'] = { options: { processContent: false }, files: [ { expand: true, flatten: true, src: ['out/' + chain + '/ios/MobileStore/Build/**.ipa'], dest: 'beta-assets/' + chain + '/', filter: 'isFile' } ] }; packageConfig.push('copy:' + 'ios_build_' + chain + '_ipa'); // drop plist gruntConfig.copy['ios_build_' + chain + '_plist'] = { options: { processContent: false }, files: [ { expand: true, flatten: true, src: ['out/' + chain + '/ios/MobileStore/Build/**.plist'], dest: 'beta-assets/' + chain + '/', filter: 'isFile' } ] }; packageConfig.push('copy:' + 'ios_build_' + chain + '_plist'); // drop html gruntConfig.copy['ios_build_' + chain + '_html'] = { options: { processContent: false }, files: [ { expand: true, flatten: true, src: ['out/' + chain + '/ios/MobileStore/Build/install.html'], dest: 'beta-assets/' + chain + '/', filter: 'isFile' } ] }; packageConfig.push('copy:' + 'ios_build_' + chain + '_html'); // drop png gruntConfig.copy['ios_build_' + chain + '_png'] = { options: { processContent: false }, files: [ { expand: true, flatten: true, src: ['out/' + chain + '/ios/MobileStore/*.png'], dest: 'beta-assets/' + chain + '/', filter: 'isFile' } ] }; packageConfig.push('copy:' + 'ios_build_' + chain + '_png'); }); grunt.initConfig(gruntConfig); grunt.registerTask('package', packageConfig); // Default task. grunt.registerTask('default', 'package'); }; 

基本上,它删除所有的本地和合并到out / clientorchainid文件夹为每个客户端的构build。 然后将构build结果复制到beta-assets / clientorchainid文件夹以准备进行远程部署。

Build是其他任务的列表。 你可以做这样的事情(coffescript):

 grunt.registerTask "build", [ "clean:dist", "jade:html", "clientTemplates", "useminPrepare", "concurrent:dist", "copy:prerequire", "requirejs", "cssmin", "concat", "uglify", "copy:dist", "rev", "usemin" ] 

这样你可以定制自定义任务和自定义构build等