我现在正在构建一个项目,它将是一个 web 应用程序(可在浏览器上运行)和一个 Phonegap 应用程序(iOS 和 Android)。尽管从理论上讲,我的项目可以使用与我的 Yeoman 相同的 dist 文件夹。 生成, Grunt 通过运行 grunt build 任务 build 生产就绪代码。我想运行类似 grunt build_web、grunt build_ios 和 grunt build_android 的程序,分别为每个平台构建生产代码。或者 grunt build:web、grunt build:ios、grunt build:android。这样,我就可以自定义一些加载的脚本、图像等,每个脚本、图像等都有自己的构建指令。
那么,我是否应该通过 (试过了,没用)dist 和 build 指令负责任地完成我的 Gruntfile 复制和粘贴?或者,对此有最佳实践吗?
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, e.g.
//
// <!-- 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 推荐的内容之外,确实没有最佳实践。对于多个客户端,您可能需要修改您的 gruntjs。这是我的项目结构,其中 Assets 文件夹包含不同客户的数据:
这是我的 gruntjs 文件。它使用 ImageMagik 创建所有必要的启动器图标。为无线安装构建和部署。你明白了……
/***
* 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');
};
基本上,它会删除所有 native 并合并到 out/clientorchainid 文件夹中,以便为每个客户端进行构建。然后将构建结果复制到 beta-assets/clientorchainid 文件夹中,为远程部署做准备。
关于javascript - 使用 Grunt (Yeoman) 的多个构建文件夹(多个客户端、多任务、多个目标),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18449591/
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于
我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时
我正在尝试使用ruby和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t