How to Include Third-Party Frameworks in Client-Side JavaScript

frameworksjavascriptorganizationweb-development

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.

Best Answer

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.

Related Topic