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 orjavascript
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 usessass
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.
2 Answers 2
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:
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:
- grunt-contrib-jshint
- grunt-contrib-uglify
- grunt-contrib-sass
- grunt-contrib-less
- 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:
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 thesass
/less
generated files ... just slap them into/temp
and combine them at some point during my build process).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 asass
version. Problem solved (hopefully). :)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.
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.
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.
-
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– cr02013年07月29日 14:10:26 +00:00Commented Jul 29, 2013 at 14:10
Explore related questions
See similar questions with these tags.