I am really struggling to structure the Sass files in a Rails 3.1 project to avoid massive duplication ...
I have the following structure, with application.css.scss being imported as part of my main layout:
application.css.scss
- [*= require] main.css.scss
- [#import] 'variables';
- [#import]'bootstrap_overrides';
- [#import]'bootstrap';
- [#import]'base_elements';
- [#import]'superstructure';
- [#import]'modules';
So far so good. All these files are combined by sprockets into a single document. However I then want to modularise my Sass further, with page-specific files or files shared across regions of my site.
So on my GalleryResource#show page I need to use additional Sass files:
resource.scss
gallery_resource.scss
badges.scss
And maybe a css file from a lib:
gallery_lib.scss
These files need to reference a number of the files already imported in application.css. They need to make use of variables defined in variables.css.scss and of mixins defined in bootstrap. So I am forced to reimport these files in each file that uses them, causing massive duplication. I could write a manifest file for each and every page, but this is a maintenance nightmare and still results in duplication and two css files; application.css and page_specific.css.
So what is the solution? Do I need to dispence with application.css and move its imports into each page-specific file? So using the above example I would end up with a single manifest file that looks like this:
gallery_resource_manifest.css.scss
- [*= require] gallery_lib.css
- [*= require] gallery_resource.css.scss
- [#import] 'variables';
- [#import]'bootstrap_overrides';
- [#import]'bootstrap';
- [#import]'base_elements';
- [#import]'superstructure';
- [#import]'modules';
- [#import]'resource';
- [#import]'gallery_resource';
- [#import]'gallery';
- [#import]'badges';
I also attempted to do the same thing you are mentioning when organizing styles. Rails almost pushes you in this direction by constantly generating controller based stylesheets for you. In the end I've opted for adding everything to application.sass. The main advantage to this method is that you only make one request on page load and you don't end up having to re-import code that the user requested on the last page.
My personal opinion would be that if you abstract your CSS well you only need to use one stylesheet as you don't have a lot of view specific code to bring into the stylesheet in the first place. Either way, the alternative (randomly loading up modules based on page needs while you may have done the same on the previous page) doesn't sound like it's solving that problem anyway. Plus you are making more requests for completely new files.
CSS can be kind of tricky to organize. Here is an example of how I would typically organize my styles and also my stylesheets directory. This method works for a single namespace (application) or it can be broken out to work for multiple namespaces if need be (i.e. admin, application).
application.sass
// vendor ----------------------------------------------------------------------
// compass
#import "compass/reset"
#import "compass/css3/box-shadow"
#import "compass/css3/border-radius"
#import "compass/css3/box-sizing"
#import "compass/css3/opacity"
#import "compass/layout/sticky-footer"
#import "compass/utilities"
// grid
#import "susy"
// application -----------------------------------------------------------------
// base
// for mixins, variables and helpers to aid in building
// the application. no styling of actual elements should
// go in base.
#import "base/colors"
#import "base/fonts"
#import "base/mixins"
#import "base/grid"
// layout
// generic site styles. this includes site chrome styles
// such as the header, footer, flash messages, nav, as well
// as common patterns used around the site like forms, type,
// links, etc. the loose idea is that you could take these
// styles and create a similar "layout" but it wouldn't
// include contextual patterns like books, originals, etc.
#import "layout/buttons"
#import "layout/content"
#import "layout/header"
#import "layout/flash"
#import "layout/forms"
#import "layout/footer"
#import "layout/links"
#import "layout/tooltips"
#import "layout/typography"
// modules
// elements used on multiple pages that are loosely tied
// to our domain and data structure. examples are dependent
// on the needs of your site. as a general guideline, all
// modules should be used on multiple pages. elements used
// on a single page should be included in views.
#import "modules/articles"
#import "modules/buttons"
#import "modules/forms"
#import "modules/links"
#import "modules/pagination"
#import "modules/users"
#import "modules/stats"
#import "modules/some_other_domain_specific_styling_unique_to_your_site"
// views
// elements used on a single page. these styles are
// often needed to help put finishing touches on a
// page where modules didn't quite line up perfectly,
// or where a page has completely unique elements.
// these styles could often be abstracted to modules
// but lack a reason to do so. keeping them here helps
// explain their singular usage.
#import "views/welcome/index"
#import "views/settings/all"
#import "views/articles/show"
#import "views/users/show"
Organizing your styles in this manner helps to create a single responsibility for each file (which it looks like you are trying to do as well). I've found that this makes your CSS a lot more maintainable. Also, if you need to add media queries or target a specific browser with a given module it gives you a place to do so that is contextually relevant while still keeping your code legible and separate.
Note, the example above would be used if you only had one namespace. If you had an admin section (or something else) you could stick all of these directories/files inside of an "application" directory and create a similar structure for "admin". This would leave you with the following structure in app/stylesheets.
- admin
- application
- admin.sass
- application.sass
You could even create a structure to share items between the two, but that may or may not be necessary. The point is that you have a lot of flexibility and organization for whatever you may need to do. This same structure can also be used for JS if you were so inclined. This circumvents the issue of reloading modules and making new requests.
FWIW, I tried to find a solution to bringing in only what I need per page (your question) but in the end I just ended up passing more bits on the whole while simultaneously increasing my number of requests. YMMV.
Hope this helps to get you on your way!
I solved this by taking a cue from rspec, and creating a _sass_helper.sass partial, which contains all my non-direct-style imports (variables, mixins, placeholders), which is then included at the top of each file. The non-code bit is important, because if _sass_helper.sass contained any styles, they would be written into each file.
This effectively allows me to get the same "Sass environment" for each of my files, without any code duplication.
I have my tree organized like so:
includes/
_variables.sass
_mixins.sass
_extends.sass
posts/
_post_partial_1.sass
_post_partial_2.sass
application.sass
posts.sass
_sass_helper.sass
Then, something like posts.sass would look like:
#import 'sass_helper'
#import 'posts/post_partial_1'
#import 'posts/post_partial_2'
.some.globally.shared.post
styles: here
It works great. The post partials don't need the sass helper, so I end up with one master file per controller, with partials for individual features, and the same environment everywhere.
Related
I'm trying to get my head around the best approach to this (or if it's even possible). I have a rather large application i have set the color sass variables globally within two separated files - _theme-dark.scss and _theme-light.scss. Now i would like to allow the user to switch between the two say via a click of a button.
Here's an example of my app.scss file:
app.scss
#import 'modules/theme-dark';
//#import 'modules/theme-light';
FYI switch the code comments between the two files manually works, no i just need to make it dynamic.
in my current meteor app I have split the less declarations in one file per Controller (iron-router). I have a common file - where I have defined some mixins - which is imported in each less file. My problem is that the classes are imported multiple times in each route.
The file structure is:
mixins.import.less (new names, reference http://docs.meteor.com/#less)
.grid-container {
// something
}
postList.less
#import (once) url('/client/views/mixins.import.less');
postDetail.less
#import (once) url('/client/views/mixins.import.less');
Then in the Chrome inspector I found duplicated everything I have written in mixins.import.less. Is it possible to avoid this double import?
Assuming you want the mixin code at least once in your compiled css (perhaps not, some just want them as mixins, not classes in the css code), then make sure you set it to bring in the "mixins.import.less" file all by itself. Then for all your dependent files using it, do this:
"postList.less", "postDetail.less", etc.
#import (reference) url('/client/views/mixins.import.less');
The (reference) option has been available since LESS 1.5, and will only bring in the code for reference purposes to be used in the LESS file, but will not itself output any css.
Meteor bundles css and js/html resources all together as a single css and a single js file in production.
In development, they are individually served, but still at the same time, during initial page load (first ever request to server)
For less files, a css file is created for each (during development). Since you are importing, what Meteor basically does is create each corresponding css file that each contain the import individually.
And when they are served to the client all together (take a look at the head section of the generated html), you end up with that many copies of the imported style declarations.
Therefore, due to this bundling behaviour of Meteor, you can just keep one copy of your less mixins in a less file, and not import at all, since they are going to be served to the client in CSS form anyway.
Also, it is possible to trick Meteor into bypassing as described in the unofficial meteor faq:
... you can change the extension of the less files to be imported from .less to .lessimport and then change your #import file.less to #import file.lessimport. This will prevent the less compiler from automatically trying to compile all your import files independently, yet still let you use them ...
In a project I have separated the CSS into two sub-projects:
C:/Projects/_Framework/css
C:/Projects/_Base/css
Note: I've purposely prefixed the above with the C:/Projects/ to illustrate that they are totally separate projects from each other.
The _Framework has basic things like reset, typography, forms, and a grid.
The _Base has the default theme for the project. _Base imports the _Framework
Inside _Base I have files like header.less and variables.less which store the design of the header and the colours for the theme.
So if I have my project at: C:/Projects/App
And inside this App project, I have a master CSS file that looks like:
#import "/_Framework/_loadAll.less
#import "/_Base/_loadAll.less
Which imports both the Framework and the Base theme (the reason that Base doesn't load the Framework itself is for flexibility but has scope to be done this way instead if needed).
The plan is to make it so that if a header.less file existed inside _App then it would automatically override the import of the one in _Base.
How could this be achieved?
You can't exclude a whole .less file by checking for duplicate names. Just make sure that the header.lessfile of your _App project is imported after the one from _Base.
Pseudo code:
#import "/_Framework/_loadAll.less
#import "/_Base/_loadAll.less
#import "_App/header.less"
And overwrite any properties you need in the _App/header.less file.
If you need to overwrite everything in the _Base/header.less file, I would suggest it's not a good fit for a Base style.
I am calling several styelsheets in application.css.scss which contain require directive and #import
Current
*= require font
*= require font-awesome
*= require twitter/bootstrap
#import "bourbon";
#import "app_css_that_uses_bourbon_mixins
In order to use bourbon gem (from thoughtbot) , I have to use the #import syntax. However, when i start to use #import, i am losing out the ability to view individual css files in the development mode. But, when i use *= require directive, I am able to view the css files individually in development mode. I believe this properties (of viewing individual, non concatenated files) are derived from 'config.assets.debug = true' which is the setting in my app.
Since there are several csss' I am having difficulty in debugging them when sprockets concatenates them, I would like to be able
1. Be consistent to use #import syntax
2. Be able view the individual css files for debugging in the development mode.
Want
#import "bourbon";
#import "font-awesome";
#import "twitter/bootstrap";
#import "app_css_that_uses_bourbon_mixins
.....
Appreciate any help.
I would scrap the require and stick with all import and explicitly list each file.
I'm not sure if this is any help, but you can just browse the URL for your individual stylesheets. Let's say for instance I have signin.css.scss -
I can browse to
http://localhost:3000/assets/signin.css
This will show the files contents!
I'm going to have a play with my development environment to see if I can get it to list it inside the dev tools.
I am about to do upgrades to webpages and css-files in our production environment. I would like to prevent css caching in the web browser, since this would otherwise disrupt the design if the new html is used together with the old css. I have made changes to three css-files in total:
Main css-file (referenced in the head section of the page with tag)
css-file A (referenced inside the main css-file using #import "css-fileA.css" syntax)
css-file B (referenced inside the main css-file using #import "css-fileB.css" syntax)
To prevent browser caching I have added a querystring to my main css-file like:
...link href="main.css?rev=20121024"...
I now wonder if the use of a querystring on main.css helps prevent caching of the css-files A and B that are referenced inside main.css with #import statements? Or do I need to also apply querystrings for them like #import "css-fileA.css?rev=20121024" ?
Any help is greatly appreciated.
I expect each file to be treated independently. Why would it make a difference whether css-fileA.css was loaded directly or via #import? It's still the same URL and the cache should be used the same way.