Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

When developing web apps you often build your app on top of some frameworks like Twitter's bootstrap. These frameworks come with less/sass/stylus, javascript, images and sometimes webfonts.

I'm wondering how to embed these frameworks in my app. Most of my apps are build using some grunt tasks that compile my coffeescript and stylus to javascript respectively css. Because I'm not shipping third party code (e.g. frameworks) with my app I add them as a dependency. This allows me to install e.g. frameworks using a package manager like npm or volo.

Handling small javascript libraries (which consist only of one file, no assets) is easy. I install them in a vendor dir and update my require.js conf. But if the library has images or less/sass/stylus/css files attached I'm always struggling how to handle them:

  • Paths to assets in the css files or javascript files of these libraries are often hardcoded (relative)
  • less/sass/stylus has to be compiled (which I can do with a grunt task)
  • Copy all third party assets into my folder structure?
  • Should I include third party assets (stylesheets, javascript) in my own files and minimize them together?
  • If I use stylus and the framework uses sass I have to install a lot of further dependencies

I think I'm not the only one who has these problems so I'm looking for solutions, best practices or some general tips.

share|improve this question

2 Answers 2

up vote 3 down vote accepted

I'm wondering how to embed these frameworks in my app.

Short answer:

Use aliases?

If it can be automated, I don't see any harm in copying.

Long answer:

Listed below are some of the tools and techniques I use.

Bower

Bower is a package manager for the web. It offers a generic, unopinionated solution to the problem of front-end package management, while exposing the package dependency model via an API that can be consumed by a more opinionated build stack. There are no system wide dependencies, no dependencies are shared between different apps, and the dependency tree is flat.

Grunt.js

Grunt: The JavaScript Task Runner.

grunt-bower-task

Install Bower packages. Smartly.

... Also worth mentioning:

Git submodules

Submodules allow you to keep a Git repository as a subdirectory of another Git repository.


Real world example ...

Within my Gruntfile.js, I have a bower task:

bower : {

    install : {

        options : {

            targetDir : './files/plugins', // A directory where you want to keep your Bower packages.
            cleanup : true,                // Will clean target and bower directories.
            layout : 'byComponent',        // Folder structure type.
            verbose : true,                // Debug output.

        },

    },

},

... the above requires a bower.json (next to the Gruntfile):

{
  "name": "simple-bower",
  "version": "0.0.0",
  "dependencies": {
    "normalize-css": "2.1.3",
    "foo": "https://github.com/baz/foo.git"
  },
  "exportsOverride": {
    "foo": {
      "scss": "foo/scss/",
      "css": "foo/*.css"
    }
  }
}

When the above bower task is run, I get a folder structure that looks like (organized by "component"):

  • plugins/
    • normalize-css/ (component)
      • normalize.css
    • foo/ (component)
      • css/
        • foo.css
        • foo.min.css
      • scss/
        • foo.scss
        • partials/
          • _variables.scss
          • _functions.scss
          • _mixins.scss
          • _other.scss

Note: Using exportsOverride option, I was able to "grab" resources other than the main resource that was specified via the endpoint's (foo, in this case) bower.json.

Based on the above setup, I now have a folder called plugins/ (like, I assume, your vendor/ folder) and I can now point my Gruntfile tasks at said directory and/or files.

But if the library has images or less/sass/stylus/css files attached I'm always struggling how to handle them ...

Alias technique #1:

Question: Based on the above Bower install, how does one include normalize.css as a SASS partial?

One solution is to create an alias (cwd = my project's scss/ folder):

$ ln -s ../plugins/normalize-css/normalize.css _normalize.scss

From there, I can treat the alias as an SCSS partial and @import into my scss files as I please.

Alias technique #2:

Question: Based on the above Bower install, how does one include foo's scss partials?

Assuming foo.scss includes all of the partials, it's not going to work as a direct alias because the @imports are relative.

Why not alias the whole folder then?

$ ln -s ../plugins/foo/scss ./foo

Now I can easily include the 3rd party dependency's scss partials (or just foo.scss) in my project's build process without having to do much leg work.

Copying technique ...

Copy all third party assets into my folder structure?

I see no problems with this approach either. Optimally, you'd automate the copy with a task like:

grunt-contrib-copy

One approach could be a copy target that runs via an init task which would copy source files to the locations you specify in your build directory. From there, you'd run your other/process tasks to generate/build/copy the development/production files like you would normally.

Here's the copy task from one of my latest projects:

copy : {

    // Copies `normalize.css` from `plugins/` into the root-level `/demo/` folder:
    dev : {

        filter : 'isFile',
        expand : true,
        cwd : './files/plugins/',
        src : '**',
        dest : '../demo/',
        flatten: true,

    },

    // Copies source `scss` files into my distribution folder:
    prod : {

        filter : 'isFile',
        expand : true,
        cwd : './files/styles/',
        src : [
            '**/*',
            '!demo.scss',
        ],
        dest : '../<%= pkg.name %>/scss/',

    },

    // Copies images into the root-level `/demo/` folder:
    all : {

        expand : true,
        cwd : './files/images/',
        src : '**',
        dest : '../demo/',

    },

},

Direct linking technique?

Depending on your needs, there's not much stopping you from relatively linking to 3rd party assets in your vendor/ folder. I'd say if direct linking doesn't work (due to some sort of relative import or something), then the above two aliasing options are good alternatives. Personally, I find that aliasing is nice because it keeps my path strings clean.

Minimize and/or uglify?

Should I include third party assets (stylesheets, javascript) in my own files and minimize them together?

I say, for production environment, definitely yes.

My personal approach is to have two build processes, one for "dev" and one for "prod". The former links to source files whereas the latter links to the min/uglified files.

I've given a basic outline of my build process here:

Have Grunt generate index.html for different setups

For me, the goal is to have a "dev" build for development (where I can easily use to hunt for bugs) and a "prod" build for production (keeping in mind, that new bugs may get introduced when the source files have been combined/minified/uglified).

Grunt tasks I use on a regular basis to generate my min/ugly files:

  1. grunt-contrib-jshint
  2. grunt-contrib-uglify
  3. grunt-contrib-sass
  4. grunt-contrib-less
  5. grunt-contrib-htmlmin

Note: I always strive to include un-minified/uglified versions of 3rd party code in my builds; if I decide to use already compressed files, I skip the compression step (for these files) in my build process and just do the file concatenation instead.

3rd party uses different preprocessor/other?

If I use stylus and the framework uses sass I have to install a lot of further dependencies.

That's definitely a predicament. A few solutions I can think of:

  1. Just prepare yourself for these eventualities. It's usually not too hard to install packages (homebrew helps), so once you're system is setup it should be easier the next time you encounter this type of situation. From there, it shouldn't be hard to find a Grunt task to incorporate the source files into your build. A lot of times I'll use a temp/ folder to store "inbetween" build files, and then I'll use other tasks to combine/minify/uglify said code with other code (for example, let's say I want to combine the sass/less generated files ... just slap them into /temp and combine them at some point during my build process).

  2. Find a version of that framework that's using the tools you prefer. For example, Twitter's Bootstrap uses less; well, luckily, there happens to be a sass version. Problem solved (hopefully). :)

  3. If it exists, and if you can get away with it, use the "generated" version of the framework. For example, I try to include a generated source file for every project I create ... I dunno about you, but I like having the option of viewing/using an already generated version of a plugin, especially if said plugin can work out-of-the-box in my project.

  4. Other? I'm currently out of ideas on this one.


With all that said, I am by no means an authority on this topic ... The above is just what works for me. My personal preference for when it comes to this stuff is to strike a balance between simple and complex.

share|improve this answer
2  
+5 if I could do it :)) The level of detail exemplify the best answers we can have on any S.E. site ! –  Howard Pautz Sep 25 '13 at 1:34
    
Thanks @HowardPautz! I appreciate the compliment! I try to give back to the community when/where I can. Hopefully something I said above will help someone. –  mhulse Sep 25 '13 at 2:24
1  
u r welc. yes, me too. Your answer certainly assisted me as I'm just doing noob-level work w/grunt and needing a real-world bower example. Its flat dependency tree looks it will reduce my Excedrin intake substantially :-P –  Howard Pautz Sep 25 '13 at 2:30

If you are doing this stuff for a public facing website, I would go down the path of using someone's CDN if possible. It'll probably make your site faster (if the CDN is up to scratch) and use less bandwidth for your own server. They will have set the content expiration up properly and it should be cached properly.

However if it's an internal app, you might not want to rely a third party CDN - so that your app keeps working even if your internet connection drops out. Or if you have a dodgy internet connection your app will be more reliable. In this case I would download everything, minify and bundle it together (well as much as is reasonable).

To answer your other concerns:

  • Paths to assets in the css files or javascript files of these libraries are often hardcoded (relative)

Yep, it's a pain, you're gonna have to deal with it somehow. I use ASP.NET's bundling and minification (System.Web.Optimization) which lets you set the base URL so that relative paths from within your CSS and Javascript work.

  • less/sass/stylus has to be compiled (which I can do with a grunt task)

I haven't used any of them, but sounds like you've got it covered.

  • If I use stylus and the framework uses sass I have to install a lot of further dependencies

Yep, that's one of the tradeoffs for using a framework, especially one with dependencies. It's your call if you think the benefit of using it outweighs the pain of managing it.

share|improve this answer
    
I don't think it's feasible for a production open world app to include a dozen scripts from a third party cdn. For production I always let requirejs produce a optimized version of my app and scripts on a cdn aren't included. I think I have to setup a more or less private repository for "incompatible" third party libraries with a "managed" folder structure to handle hardcoded links in libraries. Then I can set up this repository for being used by bower. Anyway thanks for your answer. –  cr0 Jul 29 '13 at 14:10

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.