-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Organize Gruntfiles by Packages / Features #1048
Comments
Yep, something I've been thinking about. |
@cowboy What do you think about the |
@scottrippey Of course I'm going to be all for it! One thing about your examples though: that's package-by-layer, e.g. grouping components by technology (JS, CSS, etc.) is a layer. To better illustrate the desire (or at least from my POV), would be to have a edit: with that said, it does demonstrate how you can configure tasks in multiple different locations, which is the end goal I believe. Perhaps it is PbF after all, just with a different definition for 'feature' than I had originally thought of. |
It's a challenge in its current state. Simple, shallow projects work well While I am vaguely uncomfortable with the It's definitely a step in the right direction and a discussion worth |
@k3n What you said makes perfect sense ... my example is flawed. In my actual application, I've got tasks like "desktop" and "mobile". Those are better examples. |
@brian-frichette The reason I like |
My current solution to this (which actually works pretty well) has been to create a subdirectory for each widget containing all its resources (js, css, etc), and put a Gruntfile in each of them. The root application then uses grunt-hub to manage these. An added benefit of this solution is that it's easy to refactor these widgets out into npm & bower modules, which will then already have a Gruntfile, as opposed to extracting out the widget's build strategy at refactor-time. A downside, of course, is that most of these Gruntfiles are identical redundant code. I'm using a Yeoman generator to create them. Edit: Also worth noting, each of my widgets contains a |
@scottrippey, Yeah, that's a pretty strong argument :-) |
@scottrippey have you tried this in a situation where you want to do // js config
grunt.mergeConfig({
concat: {
desktop: {
files: [
{ dest: 'scripts.min.js', src: ['**/*.js'] }
]
}
}
});
// css config
grunt.mergeConfig({
concat: {
desktop: {
files: [
{ dest: 'styles.min.css', src: ['**/*.css'] }
]
}
}
}); Will the |
@doowb No, I haven't tried that. In fact, I only ever intended to merge 2-levels deep, so I always use unique target names. Since I think it's pretty much impossible to know how to correctly merge duplicate targets. So, perhaps |
We could easily build the following config in a different way. grunt.init({
task1: {
options: {a: 'task1'},
target1: {options: {a: 'task1-target1'}},
target2: {options: {a: 'task1-target2'}},
},
task2: {
options: {a: 'task2'},
target1: {options: {a: 'task2-target1'}},
target2: {options: {a: 'task2-target2'}},
},
task3: {
options: {a: 'task3'},
target1: {options: {a: 'task3-target1'}},
target2: {options: {a: 'task3-target2'}},
},
}); Sure, we can set per-task config all at once: grunt.config.set('task1', {
options: {a: 'task1'},
target1: {options: {a: 'task1-target1'}},
target2: {options: {a: 'task1-target2'}},
}); But, for example, with a method like // Normal per-task config, omitting target-specific config.
grunt.init({
task1: {options: {a: 'task1'}},
task2: {options: {a: 'task2'}},
task3: {options: {a: 'task3'}},
});
// Remember that you can specify an array of property name parts like this.
grunt.config.set(['task1', 'target1'], someObject);
// So here's a new helper method. Dunno if it makes sense.
grunt.config.setByTarget = function(targetname, targetConfigs) {
for (var taskname in targetConfigs) {
grunt.config.set([taskname, targetname], targetConfigs[taskname]);
}
};
// Per-target, per-task options, grouped by target.
grunt.config.setByTarget('target1', {
task1: {options: {a: 'task1-target1'}},
task2: {options: {a: 'task2-target1'}},
task3: {options: {a: 'task3-target1'}},
});
grunt.config.setByTarget('target2', {
task1: {options: {a: 'task1-target2'}},
task2: {options: {a: 'task2-target2'}},
task3: {options: {a: 'task3-target2'}},
});
grunt.config.setByTarget('target3', {
task1: {options: {a: 'task1-target3'}},
task2: {options: {a: 'task2-target3'}},
task3: {options: {a: 'task3-target3'}},
}); Just an example... thoughts? |
@cowboy I like that it inverts tasks and targets ... targets are like features, so it makes sense. But sometimes a single feature needs multiple tasks, which would be difficult this way. |
@cowboy it's interesting that |
@scottrippey, These were thoughts precisely. @cowboy, the syntax and structure you presented is clever, but obtuse. The fact that After looking at the implementation (which is what "prickled" me, not the syntax), I am only bothered by the dependency on |
@scottrippey I have a module that does this kind of thing: gruntfile-gtx. It is a wrapper to use in your Gruntfile with many helpers to creatively build the config object (and some aliases). It also covers that case where one build 'job' uses multiple plugins that share some data (like paths or id's). My recurring example is running tests for modules of my TypeScript projects: each needs a sequence of |
@brian-frichette @cowboy I think I might have found a solution ... I reimplemented the |
@scottrippey This is much better. The merge was a weird concept since I can't think of a reason you'd want to do a deep merge. For example if you had a task w/ a files array, and tried to merge two of the same If instead, you are just assembling a variety of targets of |
@scottrippey can you show me with some code how it works? Show me:
Sell me. |
@cowboy All right ... give me a few minutes, and I'll concoct an example that'll blow your mind. |
@cowboy I got a bit busy, but I'm delivering on my promise: https://gist.github.com/scottrippey/8720672 |
That's a great example @scottrippey, thanks for working that up! Sorry I haven't contributed much here, I was pulled in another direction at work and so I had to refocus, but I think you guys are doing a good job of hashing this out. About the example itself: my use-case would have each of the |
@k3n Yes, the next logical step (which is optional, but fully recommended) is to separate each package into its own file.
|
After a few months of using this pattern, I'm extremely happy with its effect, and I'd really like to see this merged. Gulp organizes by package, tooThis was one of the first appealing things I noticed about Gulp ... by default, Gulp is already "package" oriented, and encourages organizing everything by package. So @cowboy, I'd suggest you review the following examples, if you haven't been convinced: Example Use-CaseIn the following example, I'm using Grunt to build 4 packages, This change is only a few lines of code, it's completely new (non-breaking) functionality, and it makes organization a breeze. The Pull Request #1039 is ready to go. |
@scottrippey, while a The API I had already suggested for Either way, while a general merge method might be useful, it seems like the larger problem to be solved is that people might want to configure same-named targets across multiple tasks in a single method call. My method attempts to address that specific use-case. (Also note that in this example, setting default options is done with module.exports = function(grunt) {
// Default options
grunt.initConfig({
uglify: {
options: {
report: 'min',
compress: { loops: true, unused: true, unsafe: true }
}
},
watch: {
options: { atBegin: true, livereload: true }
}
});
// Core css
grunt.config.setByTarget('core-css', {
less: {
src: 'core/**/*.less', dest: 'dist/core.css'
},
watch: {
src: 'core/**/*.less', tasks: [ 'less:core-css' ]
}
});
// Core JS
grunt.config.setByTarget('core-js', {
concat: {
src: 'core/**/*.js', dest: 'dist/core.js'
},
uglify: {
src: 'core/**/*.js', dest: 'dist/core.min.js'
},
watch: {
src: 'core/**/*.js', tasks: [ 'concat:core-js', 'uglify:core-js' ]
}
});
// Extras CSS
grunt.config.setByTarget('extras-css', {
concat: {
src: 'extras/**/*.css', dest: 'dist/extras.css'
},
watch: {
src: 'extras/**/*.css', tasks: [ 'less:extras-css' ]
}
});
// Extras JS
grunt.config.setByTarget('extras-js', {
concat: {
src: 'extras/**/*.js', dest: 'dist/extras.js'
},
uglify: {
src: 'extras/**/*.js', dest: 'dist/extras.min.js'
},
watch: {
src: 'extras/**/*.js', tasks: [ 'concat:extras-js', 'uglify:extras-js' ]
}
});
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-uglify');
}; |
I see ... the However, this seems like it will only work for multi-target tasks, right? If I have a package that requires a basic task (such as So it seems that this achieves a similar effect, but has a few limitations, and comes with a learning curve. My point is that |
I'm in favor of anything that facilitates the end-goal, but do agree with the approach from @cowboy which requires less cruft. I was actually toying with attempting to implement a similar pattern, but which would instead use the module name as the task target, which would preclude one from needing to explicitly define the task target in the code. Just for demo purposes, I'll call this imaginary method // core-css.js
module.exports = function(grunt) {
grunt.config.setByModule({
less: {
src: 'core/**/*.less', dest: 'dist/core.css'
},
watch: {
src: 'core/**/*.less', tasks: [ 'less:core-css' ]
}
});
}; Here, The reason for this is that moving things around should be easier, and it's one less thing that another developer could miss in the implementation. However, I yield that I haven't fully thought this through, and it may not even make sense for some use-cases. |
@k3n I can't for the life of me see what "cruft" you're speaking of. I think @scottrippey's points are valid, and would argue that |
The "cruft" is the fact that So that's what @cowboy and @k3n are trying to do with That's the point of I hope this helps everyone understand the difference. |
I get it, I just don't see it as "cruft". The mirroring of |
@brian-frichette I'm using "cruft" to refer to code that I believe is unnecessary and redundant. The point that @scottrippey's implementation more closely resembles the 'normal' config usage is definitely accurate, but to say that using more code is less cruft is factually incorrect IMO. @scottrippey's method would require |
Also, as I stated before, I'm not even completely sold on my own idea, I just wanted to throw it out there for discussion since we were already on the topic. It came to me when I was implementing There is currently no mechanism in place to guard against mixing task targets, so the following is completely legal although most likely not what you'd want (notice I've mixed task targets // Extras CSS
grunt.mergeConfig({
concat: {
'extras-css': {
src: 'extras/**/*.css', dest: 'dist/extras.css'
}
},
uglify: {
'extras-js': {
src: 'extras/**/*.js', dest: 'dist/extras.min.js'
}
},
watch: {
'extras-js': {
src: 'extras/**/*.js', tasks: [ 'concat:extras-js', 'uglify:extras-js' ]
}
}
}); |
I see your point, and I think I would agree if this were a different sort of framework. I'm generally not a fan of verbosity, assuming intent can adequately be described otherwise. However, it's a build tool, and there is already a significant barrier to entry. I have heard this numerous times from people trying to learn Grunt. Sure, once you get it, it's utterly simple, but that's true of many things in programming and life :) Anyways, no reason to belabor the point. I'm happy that this discussion is occurring. |
@cowboy's grunt.mergeConfig({
"mocha": {
"test-dev": {},
"test-dist": {}
}
}); "test" being the feature, but because there are two targets, I couldn't simply name it "test". Why not just implement both? If over-complication is an argument, then I'd say just go with |
Maybe I'm totally missing something important here, but with the plugin I wrote load-grunt-configs you can do this w/o the need for an extra API method. You do have to put them into separate files though. |
@creynders Your plugin looks great, and it definitely achieves organizing by package. Thanks for sharing. |
Relevant to this subject: I've been working on a utility library that provides an alternate syntax for Gruntfiles that similarly groups by package and additionally, execution order: https://github.com/dallonf/bacon-grunt That said, this has a much larger learning curve than |
This was, in fact, added to Grunt v0.4.5 -- it includes |
Grunt's config file is organized by tasks.
This makes it very difficult to organize. Even if split into separate task files, it's likely that a single feature will span several tasks, and multiple features will overlap tasks.
The concept of Package by feature, not layer would greatly improve the way we organize our Gruntfile configuration.
@cowboy @stevenvachon @k3n @brian-frichette Thoughts?
The text was updated successfully, but these errors were encountered: