EmberJS & SASS & Bootstrap

EmberJS & SASS & Bootstrap

Because I have figured this out more than once... I present my notes on how to setup an ember-cli project with sass css.

Additionally I will add the bootstrap framework and a pattern to be able to override it's .scss sources. I focus on the Bootstrap framework but the above should work with any library that have sources in sass.

Finally I describe a tiny pattern I have found useful regarding directories and css.

First we'll init a ember project. You can do this with any existing ember project, but please note that ember-cli only allows to use one styling language.

mkdir new-app
cd new-app
ember init
npm uninstall ember-welcome-page --save-dev

SASS

It is not difficult to switch to SASS support to in ember project. First some dependencies.

npm install broccoli-funnel --save-dev
npm install broccoli-merge-trees --save-dev
bower install bootstrap-sass --save
ember install ember-cli-sass

Then it is necessary must modify the ember-cli-build.js file to be able to generate css from .sass (or .scss) files.

I define the includePaths option of ember-cli-sass to bower_components/bootstrap-sass/assets/stylesheets. This simply makes it possible to do @import 'bootstrap/*'; within our application styles file.

A 'Funnel' is created to be able to use the bootstrap icon fonts.

/*jshint node:true*/
/* global require, module */
var EmberApp = require('ember-cli/lib/broccoli/ember-app');
var Funnel = require('broccoli-funnel');
var MergeTrees = require('broccoli-merge-trees');

module.exports = function (defaults) {
  var app = new EmberApp(defaults, {
    sassOptions: {
      includePaths: [
        'bower_components/bootstrap-sass/assets/stylesheets'
      ]
    }
  });

  // Use `app.import` to add additional libraries to the generated
  // output files.
  //
  // If you need to use different assets in different
  // environments, specify an object as the first parameter. That
  // object's keys should be the environment name and the values
  // should be the asset to use in that environment.
  //
  // If the library that you are including contains AMD or ES6
  // modules that you would like to import into your application
  // please specify an object with the list of modules as keys
  // along with the exports of each module as its value.

  // @see https://ember-cli.com/user-guide/#using-broccoli-funnel
  var bootstrapAssets = new Funnel(app.bowerDirectory + '/bootstrap-sass/assets/fonts/bootstrap', {
    srcDir: '/',
    include: ['**/*.*'],
    destDir: '/fonts/bootstrap'
  });

  return app.toTree(new MergeTrees([bootstrapAssets]));
};

Finally renaming app/styles/app.css to app/styles/app.scss enables us to do something like this @import "bootstrap/normalize"; and have the sass preprocessor find the correct file.

mv app/styles/app.css app/styles/app.scss

A directory pattern for styles

Organizing style segments is kind of hard. I usually start out cleanly but things get muddier as I build my app. Here is a directory structure as a suggestion.

mkdir app/styles/bootstrap
mkdir app/styles/bootstrap/overrides
mkdir app/styles/mixins 
mkdir app/styles/body-classes

And some files

echo '// application variables' > app/styles/variables.scss
echo '@import "variables";' > app/styles/app.scss
echo '@import "bootstrap/bootstrap";' >> app/styles/app.scss
cp ./bower_components/bootstrap-sass/assets/stylesheets/bootstrap/_variables.scss app/styles/bootstrap/overrides/variables.scss

Add the following (as needed) to app/styles/app.scss

@import "variables";
@import "bootstrap/variables";
@import "bootstrap/overrides/variables";

@import "bootstrap/mixins";
// Reset and dependencies
@import "bootstrap/normalize";
@import "bootstrap/print";
@import "bootstrap/glyphicons";

// Core CSS
@import "bootstrap/scaffolding";
@import "bootstrap/type";
@import "bootstrap/code";
@import "bootstrap/grid";
@import "bootstrap/tables";
@import "bootstrap/forms";
@import "bootstrap/buttons";

// Components
@import "bootstrap/component-animations";
@import "bootstrap/dropdowns";
@import "bootstrap/button-groups";
@import "bootstrap/input-groups";
@import "bootstrap/navs";
@import "bootstrap/navbar";
@import "bootstrap/breadcrumbs";
@import "bootstrap/pagination";
@import "bootstrap/pager";
@import "bootstrap/labels";
@import "bootstrap/badges";
@import "bootstrap/jumbotron";
@import "bootstrap/thumbnails";
@import "bootstrap/alerts";
@import "bootstrap/progress-bars";
@import "bootstrap/media";
@import "bootstrap/list-group";
@import "bootstrap/panels";
@import "bootstrap/responsive-embed";
@import "bootstrap/wells";
@import "bootstrap/close";

// Components w/ JavaScript
@import "bootstrap/modals";
@import "bootstrap/tooltip";
@import "bootstrap/popovers";
@import "bootstrap/carousel";

// Utility classes
@import "bootstrap/utilities";
@import "bootstrap/responsive-utilities";
@import "bootstrap/theme";

Copy any bootstrap scss file so you can override properties and css definitions in each of them.

You'll need to import them from app/styles/app.scss. Beware of the order you import them.

Body classes

A pattern that I found useful makes use of routes that upon activation add classes to the body tag.

ember install ember-body-class

All routes have a classNames attribute of type Array. If you wanted to add a body class page-special to your route, it would look like this:

export default Ember.Route.extend({
  classNames: ["page-common", "page-special"]
})

Then I create a file per use body class and import them in app/styles/app.scss.

// app/styles/pages/page-common.scss
body.page-common {
// a things are black
}
// app/styles/pages/page-special.scss
body.page-special {
// special things are red
}

The Ember way to Bootstrap

Since we've now added bootstrap css compilation to our workflow it might be an idea to also add these ember-bootstrap integrations.

ember install ember-bootstrap
ember install ember-bootstrap-cp-validations

To avoid adding ember-bootstrap compiled css modify ember-cli-build.js to instruct it not to include it.

'ember-bootstrap': {
  'importBootstrapCSS': false,
  'importBootstrapTheme': false
},

Vendor CSS Prefixing

Completely free bonus step

Install the ember-cli-autoprefixer addon so you can simply write the css you know and forget about vendor prefixing.

ember install ember-cli-autoprefixer

Then add a list of browsers (per environment target) to your packages.json so the prefixer (and other npm modules) can do its magic.

See browserlist for more information on this subject.

"browserslist": {
  "production": [
    "last 2 version",
    "ie 10"
  ],
  "testing": [
    "last 2 versions"
  ],
  "development": [
    "last 2 versions"
  ]
}