I recently had the same question myself. I couldn't really find much online regarding this so I decided to deep dive into Grunt, Node, and Git.
Grunt and Node
In the root folder of my Cordova projects (alongside the www, platforms, merges, and plugins folders) I have a grunt project. I store modular JavaScript files and CSS files that may or may not be platform dependent inside a src folder inside the root of the Cordova project. This grunt project builds the JavaScript and CSS stored in the src folder and places the resulting files into the www or merges folder as appropriate.
I even wrote a grunt task that will make a production build of the app by optimizing and minifying css, javascript, and html templates, then rebuilds the Cordova platform projects to include these production files.
Because there is no #IF_DEF in JavaScript, I have to use Grunt (mentioned above) to build out the JavaScript for each platform. I modularize almost all of the JavaScript functions by platform. If there are no platform differences for that function (no native hooks), I only need one file. If there are differences, I need to create a separate file for each platform for that function. For example, my Cordova onDeviceReady and onResume function usually differ from platform to platform. I create files called
CordovaEvents-ios.js and CordovaEvents-android.js
Inside the package.json file, I describe the "features" I want in my app. CordovaEvents is an example of a feature.
I also support "arguments". I use the same approach above in the file names of the modular files. An argument could look like this
CordovaEvents-ios--production.js and CordovaEvents-android--production.js
In this case the code needed in a production app will differ from non production code. I can simply pass this argument (or list of arguments) during the build process, and the correct files will be picked up and built. I think this most closely addresses your question if there is an approach to maintaining a codebase that can adapt to different platforms and requirements. The requirements are the feature names and arguments. The argument can be anything you want, maybe using one plugin or another plugin.
I also describe the platforms I want to support ("iOS", "Android", and "Desktop"). Grunt processes each platform in a MultiTask, and then looks at the features I want to support. It will try to find a file called feature-platform.js. If it cannot find this file, it will just try to look for feature.js. After I concatenate all the functions together needed for the platform, it will copy the file over to the merges folder for iOS or Android. Typically all the files kept in the WWW folder in the root project will work on a desktop because there are no native Cordova hooks... this allows me to debug in Chrome. All the files that contain native hooks are stored in the merges folder. Once the Cordova project is built for the respective platform, the code can be tested in a device simulator.
If you are new to Grunt I would strongly suggest taking a look a their getting started guide.
http://gruntjs.com/getting-started
Git
I also employed Git to version my code. I used SourceTree to create the Git repository in the root folder of my Cordova project, and have a remote repository on my Mac Mini server that I can push my commits too. This allows us to have a backup copy of the code, and makes it so my team can also work on the code out-of-band.
I Git ignore the following folders:
- node_modules
- plugins
- platforms
- plugins
All the other files and folders contained in the root directory of my Cordova project are versioned. I used the following link as a reference to help define my Git strategy for Cordova:
https://leanpub.com/developingwithcordovacli/read#version-control
It didn't take me overnight to figure all this stuff out, in fact it took a little bit more than two months. I hope my answer can server as a guide to you and others to address your concerns you mentioned in your opening post.